From 1a7acfd59f48522f0dda984b2f33d20d843ee8ba Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Thu, 5 May 2016 16:34:45 -0400 Subject: [PATCH 001/145] set up role.py --- pycanvas/role.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 pycanvas/role.py diff --git a/pycanvas/role.py b/pycanvas/role.py new file mode 100644 index 00000000..fd9c17e9 --- /dev/null +++ b/pycanvas/role.py @@ -0,0 +1,7 @@ +from canvas_object import CanvasObject +from util import combine_kwargs + +class Role(CanvasObject): + + def __str__(self): + return "" From 992b2d06886eebc81d967ec43347375e4c0eed07 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Fri, 6 May 2016 13:16:42 -0400 Subject: [PATCH 002/145] wrote mehtod for list roles --- pycanvas/account.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pycanvas/account.py b/pycanvas/account.py index 747000ce..4f444216 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -286,6 +286,23 @@ def update(self, **kwargs): else: return False + def list_roles(self, account_id): + """ + List the roles available to an account + :calls: `GET /api/v1/accounts/:account_id/roles + + :rtype: Role + """ + from role import Role + + return PaginatedList( + Role, + self._requester, + 'GET', + 'accounts/%s/roles' % (account_id) + ) + pass + class AccountNotification(CanvasObject): def __str__(self): From 87902565afdc324a737cbe530955142e269a3762 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Fri, 6 May 2016 13:16:58 -0400 Subject: [PATCH 003/145] added obj for list roles --- tests/fixtures/account.json | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json index e3f1519f..7e4b2af9 100644 --- a/tests/fixtures/account.json +++ b/tests/fixtures/account.json @@ -93,5 +93,38 @@ } ], "status_code": 200 + }, + "list_roles": { + "method": "GET", + "endpoint": "accounts/1/roles", + "data": [ + { + "id": 1, + "account_id": 2 + }, + { + "id": 3, + "account_id": 4 + } + ], + "headers": { + "Link": "; rel=\"next\"" + }, + "status_code": 200 + }, + "list_roles_2": { + "method": "GET", + "endpoint": "accounts/1/roles/?page=2&per_page=2", + "data": [ + { + "id": 5, + "account_id": 6 + }, + { + "id": 7, + "account_id": 8 + } + ], + "status_code": 200 } } From b5a700da6264661cbedff91b8289f1f27c84b3e1 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Fri, 6 May 2016 13:29:37 -0400 Subject: [PATCH 004/145] added test for list roles --- tests/test_account.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/test_account.py diff --git a/tests/test_account.py b/tests/test_account.py new file mode 100644 index 00000000..a0b14e24 --- /dev/null +++ b/tests/test_account.py @@ -0,0 +1,32 @@ +import unittest +import settings +import requests_mock + +from util import register_uris +from pycanvas import Canvas +from pycanvas.role import Role + + +class TestAccount(unittest.TestCase): + """ + Tests core Account functionality + """ + @classmethod + def setUpClass(self): + requires = { + 'account': ['get_by_id', 'list_roles', 'list_roles_2'], + 'generic': ['not_found'] + } + + adapter = requests_mock.Adapter() + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) + register_uris(settings.BASE_URL, requires, adapter) + + self.account = self.canvas.get_account(1) + + def list_roles(self): + roles = self.account.list_roles() + role_list = [role for role in roles] + + assert len(role_list) == 4 + assert isinstance(role_list[0], Role) From 203e01a17b742422b6cc4c604683e154a6b98405 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Fri, 6 May 2016 13:45:56 -0400 Subject: [PATCH 005/145] added method for get a single role --- pycanvas/account.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index 4f444216..8b83dd22 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -291,9 +291,8 @@ def list_roles(self, account_id): List the roles available to an account :calls: `GET /api/v1/accounts/:account_id/roles - :rtype: Role + :rtype: :class: `PaginatedList` of :class: `Role` """ - from role import Role return PaginatedList( Role, @@ -301,7 +300,21 @@ def list_roles(self, account_id): 'GET', 'accounts/%s/roles' % (account_id) ) - pass + + def get_role(self, account_id, role_id): + """ + Retrieve information about a single role + :calls: `GET /api/v1/accounts/:account_id/roles/:id + https://canvas.instructure.com/doc/api/roles.html#method.role_overrides.show` + :rtype: Role + """ + from role import Role + + response = self._requester.request( + 'GET', + 'accounts/%s/roles/%s' % (account_id, role_id) + ) + return Role(self._requester, response.json()) class AccountNotification(CanvasObject): From 73f6d488b5b6b312d16c6e173bae4e3ce9413a87 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Fri, 6 May 2016 13:48:27 -0400 Subject: [PATCH 006/145] Added obj --- tests/fixtures/account.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json index 7e4b2af9..fc6bb542 100644 --- a/tests/fixtures/account.json +++ b/tests/fixtures/account.json @@ -126,5 +126,15 @@ } ], "status_code": 200 + }, + "get_role": { + "method": "GET", + "endpoint": "accounts/1/roles/3", + "data": { + "id": 1, + "account_id": 2, + "role_id": 3 + }, + "status_code": 200 } } From 30009ec506c26c8c3f0b0418c3273c035068ec53 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Fri, 6 May 2016 13:49:37 -0400 Subject: [PATCH 007/145] removed unused imports --- pycanvas/role.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycanvas/role.py b/pycanvas/role.py index fd9c17e9..ee9f9d40 100644 --- a/pycanvas/role.py +++ b/pycanvas/role.py @@ -1,5 +1,5 @@ from canvas_object import CanvasObject -from util import combine_kwargs + class Role(CanvasObject): From b8890ca35657a7d54593ac686104e78b26d1ae4e Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Fri, 6 May 2016 14:00:43 -0400 Subject: [PATCH 008/145] added test for get role --- tests/test_account.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_account.py b/tests/test_account.py index a0b14e24..54dd4ef2 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -14,7 +14,7 @@ class TestAccount(unittest.TestCase): @classmethod def setUpClass(self): requires = { - 'account': ['get_by_id', 'list_roles', 'list_roles_2'], + 'account': ['get_by_id', 'get_role', 'list_roles', 'list_roles_2'], 'generic': ['not_found'] } @@ -24,9 +24,14 @@ def setUpClass(self): self.account = self.canvas.get_account(1) - def list_roles(self): + def test_list_roles(self): roles = self.account.list_roles() role_list = [role for role in roles] assert len(role_list) == 4 assert isinstance(role_list[0], Role) + + def test_get_role(self): + target_role = self.account.get_role(1, 2) + + assert isinstance(target_role, Role) From 884618b1980b89d84e30bf800c8f3bcbedd94b66 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 10 May 2016 14:10:41 -0400 Subject: [PATCH 009/145] removed some stuff --- tests/fixtures/account.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json index 1476ab65..39906e17 100644 --- a/tests/fixtures/account.json +++ b/tests/fixtures/account.json @@ -264,9 +264,6 @@ ], "status_code": 200 }, -<<<<<<< HEAD - -======= "reports": { "method": "GET", "endpoint": "accounts/1/reports", @@ -458,7 +455,6 @@ "endpoint": "accounts/101", "data": {}, "status_code": 200 ->>>>>>> 9970db4553397317e3c8a0e372b7280f7325317e }, "list_roles": { "method": "GET", @@ -489,7 +485,7 @@ { "id": 7, "account_id": 8 - }, + } ], "status_code": 200 } From c2f4b975a086e19f593c1897c22b7cded02a4d4e Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 10 May 2016 14:11:09 -0400 Subject: [PATCH 010/145] fixed some testing issues --- tests/test_account.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/tests/test_account.py b/tests/test_account.py index ca9b9b0c..4a96401b 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -1,12 +1,3 @@ -<<<<<<< HEAD -import unittest -import settings -import requests_mock - -from util import register_uris -from pycanvas import Canvas -from pycanvas.role import Role -======= import datetime import unittest @@ -15,11 +6,11 @@ import settings from pycanvas import Canvas from pycanvas.account import Account, AccountNotification, AccountReport +from pycanvas.role import Role from pycanvas.course import Course from pycanvas.exceptions import RequiredFieldMissing from pycanvas.user import User from util import register_uris ->>>>>>> 9970db4553397317e3c8a0e372b7280f7325317e class TestAccount(unittest.TestCase): @@ -50,6 +41,7 @@ def setUpClass(self): register_uris(settings.BASE_URL, requires, adapter) self.account = self.canvas.get_account(1) + self.role = self.account.get_role(1,2) self.user = self.canvas.get_user(1) # __str__() From 27751853453ea39121202440f979945a88a7ca93 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 17 May 2016 13:20:41 -0400 Subject: [PATCH 011/145] Fixed tests --- pycanvas/account.py | 3 ++- tests/fixtures/account.json | 2 +- tests/test_account.py | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index bbb8eeef..37b76f90 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -292,7 +292,8 @@ def list_roles(self, account_id): :rtype: :class: `PaginatedList` of :class: `Role` """ - + from role import Role + return PaginatedList( Role, self._requester, diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json index 39906e17..f8a30659 100644 --- a/tests/fixtures/account.json +++ b/tests/fixtures/account.json @@ -361,7 +361,7 @@ }, "get_role": { "method": "GET", - "endpoint": "accounts/1/roles/3", + "endpoint": "accounts/2/roles/1", "data": { "id": 1, "account_id": 2, diff --git a/tests/test_account.py b/tests/test_account.py index 4a96401b..e86abb99 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -41,7 +41,7 @@ def setUpClass(self): register_uris(settings.BASE_URL, requires, adapter) self.account = self.canvas.get_account(1) - self.role = self.account.get_role(1,2) + self.role = self.account.get_role(2, 1) self.user = self.canvas.get_user(1) # __str__() @@ -224,13 +224,13 @@ def test_update_fail(self): assert not success def test_list_roles(self): - roles = self.account.list_roles() + roles = self.account.list_roles(1) role_list = [role for role in roles] assert len(role_list) == 4 assert isinstance(role_list[0], Role) def test_get_role(self): - target_role = self.account.get_role(1, 2) + target_role = self.account.get_role(2, 1) assert isinstance(target_role, Role) From 843ebcb45e83161f09366c7c2d31b9cc0656bd7c Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 17 May 2016 17:09:19 -0400 Subject: [PATCH 012/145] Added docstring changes --- pycanvas/account.py | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index 37b76f90..ec3a7e8f 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -288,12 +288,14 @@ def update(self, **kwargs): def list_roles(self, account_id): """ List the roles available to an account - :calls: `GET /api/v1/accounts/:account_id/roles + :calls: `GET /api/v1/accounts/:account_id/roles \ - :rtype: :class: `PaginatedList` of :class: `Role` + :param account_id: The id of the account to retrieve roles for + :type account_id: string + :rtype: :class: `pycanvas.paginated_list.PaginatedList` of :class: `pycanvas.role.Role` """ from role import Role - + return PaginatedList( Role, self._requester, @@ -304,8 +306,12 @@ def list_roles(self, account_id): def get_role(self, account_id, role_id): """ Retrieve information about a single role - :calls: `GET /api/v1/accounts/:account_id/roles/:id + :calls: `GET /api/v1/accounts/:account_id/roles/:id \ https://canvas.instructure.com/doc/api/roles.html#method.role_overrides.show` + :param account_id: The id of the account containing the role + :type account_id: string + :param role_id: The unique identifier for the role + :type role_id: int :rtype: Role """ from role import Role @@ -316,6 +322,35 @@ def get_role(self, account_id, role_id): ) return Role(self._requester, response.json()) + def create_role(self, label): + """ + Create a new course-level or account-level role + :calls: `POST /api/v1/accounts/:account_id/roles \ + https://canvas.instructure.com/doc/api/roles.html#method.role_overrides.add_role` + :rtype: Role + """ + from role import Role + + response = self._requester.request( + 'POST', + 'accounts/%s/roles' % (account_id) + ) + return Role(self._requester, response.json()) + + def deactivate_role(self, role_id): + """ + Deactivates a custom role + :calls: `DELETE /api/v1/accounts/:account_id/roles/:id \ + https://canvas.instructure.com/doc/api/roles.html#method.role_overrides.remove_role` + :rtype: Role + """ + from role import Role + + response = self._requester.request( + 'DELETE', + 'accounts/%s/roles/%s' + ) + class AccountNotification(CanvasObject): def __str__(self): # pragma: no cover From 2d58e0c71b44fccbddf0e8df92f27973d5f4e31c Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 17 May 2016 17:11:26 -0400 Subject: [PATCH 013/145] Added method for deactivate role --- pycanvas/account.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index ec3a7e8f..a79919f2 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -312,7 +312,7 @@ def get_role(self, account_id, role_id): :type account_id: string :param role_id: The unique identifier for the role :type role_id: int - :rtype: Role + :rtype: :class: `pycanvas.role.Role` """ from role import Role @@ -327,7 +327,9 @@ def create_role(self, label): Create a new course-level or account-level role :calls: `POST /api/v1/accounts/:account_id/roles \ https://canvas.instructure.com/doc/api/roles.html#method.role_overrides.add_role` - :rtype: Role + :param label: The label for the role + :type label: string + :rtype: :class: `pycanvas.role.Role` """ from role import Role @@ -342,14 +344,17 @@ def deactivate_role(self, role_id): Deactivates a custom role :calls: `DELETE /api/v1/accounts/:account_id/roles/:id \ https://canvas.instructure.com/doc/api/roles.html#method.role_overrides.remove_role` - :rtype: Role + :param role_id: The unique id for the role + :type role_id: + :rtype: :class: `pycanvas.role.Role` """ from role import Role response = self._requester.request( 'DELETE', - 'accounts/%s/roles/%s' + 'accounts/%s/roles/%s' % (account_id, role_id) ) + return Role(self._requester, response.json()) class AccountNotification(CanvasObject): From 8f738f5d8cf3f58a1abeb61725553a4ba008e8c4 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 17 May 2016 17:32:02 -0400 Subject: [PATCH 014/145] Wrote fixtures for new methods --- tests/fixtures/account.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json index f8a30659..e8304a23 100644 --- a/tests/fixtures/account.json +++ b/tests/fixtures/account.json @@ -488,5 +488,23 @@ } ], "status_code": 200 + }, + "create_role":{ + "method": "POST", + "endpoint": "accounts/1/roles", + "data":{ + "id": 1, + "label": 2 + }, + "status_code": 200 + }, + "deactivate_role":{ + "method": "DELETE", + "endpoint": "accounts/1/roles/2", + "data": { + "id": 1, + "role_id": 2 + }, + "status_code": 200 } } From 1a3bab5ed9c0a7b5f2a36c70776227c5629efb2c Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 17 May 2016 17:47:43 -0400 Subject: [PATCH 015/145] formatting changes --- tests/test_account.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_account.py b/tests/test_account.py index e86abb99..db87a543 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -22,8 +22,9 @@ def setUpClass(self): requires = { 'account': [ 'close_notification', 'create_course', 'create_2', - 'create_notification', 'create_subaccount', - 'create_user', 'delete_user', 'get_by_id', + 'create_notification', 'create_role', + 'create_subaccount', 'create_user', + 'deactivate_role', 'delete_user', 'get_by_id', 'get_by_id_2', 'get_by_id_3', 'get_courses', 'get_courses_page_2', 'get_role', 'list_roles', 'list_roles_2', 'reports', 'reports_page_2', From f22b80572b400f65ccc0c9849c56b40880c97a73 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 17 May 2016 17:50:13 -0400 Subject: [PATCH 016/145] wrote test for create role --- tests/test_account.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_account.py b/tests/test_account.py index db87a543..80411c74 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -235,3 +235,9 @@ def test_get_role(self): target_role = self.account.get_role(2, 1) assert isinstance(target_role, Role) + + def test_create_role(self): + new_role = self.account.create_role(2) + + assert isinstance(role, Role) + From 8d6cdc2003a8ec9577eda5d46649c0c1d9161625 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 17 May 2016 17:51:54 -0400 Subject: [PATCH 017/145] Wrote test for deactivate role --- tests/test_account.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_account.py b/tests/test_account.py index 80411c74..3661f67d 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -239,5 +239,10 @@ def test_get_role(self): def test_create_role(self): new_role = self.account.create_role(2) - assert isinstance(role, Role) + assert isinstance(new_role, Role) + + def test_deactivate_role(self): + old_role = self.account.deactivate_role(2) + + assert isinstance(old_role, Role) From a348bab2f39dc484d114db6fda8cdfcfdd46888b Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 18 May 2016 12:30:01 -0400 Subject: [PATCH 018/145] fixed tests --- pycanvas/account.py | 4 ++-- tests/fixtures/account.json | 10 ++++++---- tests/test_account.py | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index a79919f2..0914d2ce 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -322,7 +322,7 @@ def get_role(self, account_id, role_id): ) return Role(self._requester, response.json()) - def create_role(self, label): + def create_role(self, account_id, label): """ Create a new course-level or account-level role :calls: `POST /api/v1/accounts/:account_id/roles \ @@ -339,7 +339,7 @@ def create_role(self, label): ) return Role(self._requester, response.json()) - def deactivate_role(self, role_id): + def deactivate_role(self, account_id, role_id): """ Deactivates a custom role :calls: `DELETE /api/v1/accounts/:account_id/roles/:id \ diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json index e8304a23..0a2f370c 100644 --- a/tests/fixtures/account.json +++ b/tests/fixtures/account.json @@ -491,19 +491,21 @@ }, "create_role":{ "method": "POST", - "endpoint": "accounts/1/roles", + "endpoint": "accounts/3/roles", "data":{ "id": 1, - "label": 2 + "label": 2, + "account_id": 3 }, "status_code": 200 }, "deactivate_role":{ "method": "DELETE", - "endpoint": "accounts/1/roles/2", + "endpoint": "accounts/3/roles/1", "data": { "id": 1, - "role_id": 2 + "role_id": 2, + "account_id": 3 }, "status_code": 200 } diff --git a/tests/test_account.py b/tests/test_account.py index 3661f67d..8b9c0503 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -237,12 +237,12 @@ def test_get_role(self): assert isinstance(target_role, Role) def test_create_role(self): - new_role = self.account.create_role(2) + new_role = self.account.create_role(3, 2) assert isinstance(new_role, Role) def test_deactivate_role(self): - old_role = self.account.deactivate_role(2) + old_role = self.account.deactivate_role(3, 1) assert isinstance(old_role, Role) From e55699867db3b0ed10e1d2b4d3cf4d06d7139576 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 18 May 2016 13:14:04 -0400 Subject: [PATCH 019/145] wrote method for update and activate --- pycanvas/account.py | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index 0914d2ce..cb8a76a2 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -345,7 +345,7 @@ def deactivate_role(self, account_id, role_id): :calls: `DELETE /api/v1/accounts/:account_id/roles/:id \ https://canvas.instructure.com/doc/api/roles.html#method.role_overrides.remove_role` :param role_id: The unique id for the role - :type role_id: + :type role_id: int :rtype: :class: `pycanvas.role.Role` """ from role import Role @@ -356,6 +356,39 @@ def deactivate_role(self, account_id, role_id): ) return Role(self._requester, response.json()) + def activate_role(self, role_id): + """ + Reactivates an inactive role + :calls: `POST /api/v1/accounts/:account_id/roles/:id/activate \ + https://canvas.instructure.com/doc/api/roles.html#method.role_overrides.activate_role` + :param role_id: Id for the role + :type role_id: int + :rtype: :class: `pycanvas.role.Role` + """ + from role import Role + + response = self._requester.request( + 'POST', + 'accounts/%s/roles/%s/activate' % (self.id, role_id) + ) + return Role(self._requester, response.json()) + + def update_role(self, role_id): + """ + Updates permissions for an existing role + :calls: `PUT /api/v1/accounts/:account_id/roles/:id + https://canvas.instructure.com/doc/api/roles.html#method.role_overrides.update` + :rtype: :class: `pycanvas.role.Role` + """ + from role import Role + + response = self._requester.request( + 'PUT', + 'accounts/%s/roles/%s' % (self.id, role_id) + ) + return Role(self._requester, response.json()) + + class AccountNotification(CanvasObject): def __str__(self): # pragma: no cover From 819ae449e4ef9cd560d2d8bebb8bd05559eb9519 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 18 May 2016 13:21:23 -0400 Subject: [PATCH 020/145] style changes --- pycanvas/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index cb8a76a2..2d07e900 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -376,7 +376,7 @@ def activate_role(self, role_id): def update_role(self, role_id): """ Updates permissions for an existing role - :calls: `PUT /api/v1/accounts/:account_id/roles/:id + :calls: `PUT /api/v1/accounts/:account_id/roles/:id \ https://canvas.instructure.com/doc/api/roles.html#method.role_overrides.update` :rtype: :class: `pycanvas.role.Role` """ From 673d7a79c6779ae1e8d7ac9732076e1ad7813349 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 18 May 2016 13:44:02 -0400 Subject: [PATCH 021/145] removed all account_ids --- pycanvas/account.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index 2d07e900..ba73ffb3 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -285,12 +285,13 @@ def update(self, **kwargs): else: return False - def list_roles(self, account_id): + def list_roles(self): """ List the roles available to an account :calls: `GET /api/v1/accounts/:account_id/roles \ - :param account_id: The id of the account to retrieve roles for + :param account_id: The id of the account to retrieve roles + for :type account_id: string :rtype: :class: `pycanvas.paginated_list.PaginatedList` of :class: `pycanvas.role.Role` """ @@ -300,10 +301,10 @@ def list_roles(self, account_id): Role, self._requester, 'GET', - 'accounts/%s/roles' % (account_id) + 'accounts/%s/roles' % (self.id) ) - def get_role(self, account_id, role_id): + def get_role(self, role_id): """ Retrieve information about a single role :calls: `GET /api/v1/accounts/:account_id/roles/:id \ @@ -318,11 +319,11 @@ def get_role(self, account_id, role_id): response = self._requester.request( 'GET', - 'accounts/%s/roles/%s' % (account_id, role_id) + 'accounts/%s/roles/%s' % (self.id, role_id) ) return Role(self._requester, response.json()) - def create_role(self, account_id, label): + def create_role(self, label): """ Create a new course-level or account-level role :calls: `POST /api/v1/accounts/:account_id/roles \ @@ -335,11 +336,11 @@ def create_role(self, account_id, label): response = self._requester.request( 'POST', - 'accounts/%s/roles' % (account_id) + 'accounts/%s/roles' % (self.id) ) return Role(self._requester, response.json()) - def deactivate_role(self, account_id, role_id): + def deactivate_role(self, role_id): """ Deactivates a custom role :calls: `DELETE /api/v1/accounts/:account_id/roles/:id \ @@ -352,7 +353,7 @@ def deactivate_role(self, account_id, role_id): response = self._requester.request( 'DELETE', - 'accounts/%s/roles/%s' % (account_id, role_id) + 'accounts/%s/roles/%s' % (self.id, role_id) ) return Role(self._requester, response.json()) From 28b1e81a2926917aa7c008366924367724407aa0 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 18 May 2016 13:47:44 -0400 Subject: [PATCH 022/145] removed account id --- tests/fixtures/account.json | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json index 0a2f370c..bca6fcc0 100644 --- a/tests/fixtures/account.json +++ b/tests/fixtures/account.json @@ -361,11 +361,10 @@ }, "get_role": { "method": "GET", - "endpoint": "accounts/2/roles/1", + "endpoint": "accounts/1/roles/2", "data": { "id": 1, - "account_id": 2, - "role_id": 3 + "role_id": 2 }, "status_code": 200 }, @@ -461,12 +460,10 @@ "endpoint": "accounts/1/roles", "data": [ { - "id": 1, - "account_id": 2 + "id": 1 }, { - "id": 3, - "account_id": 4 + "id": 3 } ], "headers": { @@ -479,33 +476,29 @@ "endpoint": "accounts/1/roles/?page=2&per_page=2", "data": [ { - "id": 5, - "account_id": 6 + "id": 5 }, { - "id": 7, - "account_id": 8 + "id": 7 } ], "status_code": 200 }, "create_role":{ "method": "POST", - "endpoint": "accounts/3/roles", + "endpoint": "accounts/1/roles", "data":{ "id": 1, - "label": 2, - "account_id": 3 + "label": 2 }, "status_code": 200 }, "deactivate_role":{ "method": "DELETE", - "endpoint": "accounts/3/roles/1", + "endpoint": "accounts/1/roles/2", "data": { "id": 1, - "role_id": 2, - "account_id": 3 + "role_id": 2 }, "status_code": 200 } From 92f50e4cb30681f16f615ba4df0144c74e7869d1 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 18 May 2016 13:55:39 -0400 Subject: [PATCH 023/145] finished writing fixtures for remaining methods --- tests/fixtures/account.json | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json index bca6fcc0..327b3056 100644 --- a/tests/fixtures/account.json +++ b/tests/fixtures/account.json @@ -484,7 +484,7 @@ ], "status_code": 200 }, - "create_role":{ + "create_role": { "method": "POST", "endpoint": "accounts/1/roles", "data":{ @@ -493,8 +493,26 @@ }, "status_code": 200 }, - "deactivate_role":{ + "deactivate_role": { "method": "DELETE", + "endpoint": "accounts/1/roles/2/activate", + "data": { + "id": 1, + "role_id": 2 + }, + "status_code": 200 + }, + "activate_role": { + "method": "POST", + "endpoint": "accounts/1/roles/2", + "data": { + "id": 1, + "role_id": 2 + }, + "status_code": 200 + }, + "update_role": { + "method": "PUT", "endpoint": "accounts/1/roles/2", "data": { "id": 1, From 50daa7dfe53756088f9ed148e54f678745d4ef12 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 18 May 2016 14:14:12 -0400 Subject: [PATCH 024/145] fixed broken endpoints --- tests/fixtures/account.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json index 327b3056..2cc6bd1e 100644 --- a/tests/fixtures/account.json +++ b/tests/fixtures/account.json @@ -495,7 +495,7 @@ }, "deactivate_role": { "method": "DELETE", - "endpoint": "accounts/1/roles/2/activate", + "endpoint": "accounts/1/roles/2", "data": { "id": 1, "role_id": 2 @@ -504,7 +504,7 @@ }, "activate_role": { "method": "POST", - "endpoint": "accounts/1/roles/2", + "endpoint": "accounts/1/roles/2/activate", "data": { "id": 1, "role_id": 2 From 00e40aee32c726989f26d39a6d325e2926a7fd7b Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 18 May 2016 14:14:25 -0400 Subject: [PATCH 025/145] fixed tests --- tests/test_account.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/tests/test_account.py b/tests/test_account.py index 8b9c0503..3cab45d9 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -21,9 +21,9 @@ class TestAccount(unittest.TestCase): def setUpClass(self): requires = { 'account': [ - 'close_notification', 'create_course', 'create_2', - 'create_notification', 'create_role', - 'create_subaccount', 'create_user', + 'activate_role', 'close_notification', + 'create_course', 'create_2', 'create_notification', + 'create_role', 'create_subaccount', 'create_user', 'deactivate_role', 'delete_user', 'get_by_id', 'get_by_id_2', 'get_by_id_3', 'get_courses', 'get_courses_page_2', 'get_role', 'list_roles', @@ -31,7 +31,7 @@ def setUpClass(self): 'report_index', 'report_index_page_2', 'subaccounts', 'subaccounts_page_2', 'users', 'users_page_2', 'user_notifs', 'user_notifs_page_2', 'update', - 'update_fail' + 'update_fail', 'update_role' ], 'generic': ['not_found'], 'user': ['get_by_id'] @@ -42,7 +42,7 @@ def setUpClass(self): register_uris(settings.BASE_URL, requires, adapter) self.account = self.canvas.get_account(1) - self.role = self.account.get_role(2, 1) + self.role = self.account.get_role(2) self.user = self.canvas.get_user(1) # __str__() @@ -225,24 +225,33 @@ def test_update_fail(self): assert not success def test_list_roles(self): - roles = self.account.list_roles(1) + roles = self.account.list_roles() role_list = [role for role in roles] assert len(role_list) == 4 assert isinstance(role_list[0], Role) def test_get_role(self): - target_role = self.account.get_role(2, 1) + target_role = self.account.get_role(2) assert isinstance(target_role, Role) def test_create_role(self): - new_role = self.account.create_role(3, 2) + new_role = self.account.create_role(1) assert isinstance(new_role, Role) def test_deactivate_role(self): - old_role = self.account.deactivate_role(3, 1) + old_role = self.account.deactivate_role(2) assert isinstance(old_role, Role) + def test_activate_role(self): + activated_role = self.account.activate_role(2) + + assert isinstance(activated_role, Role) + + def test_update_role(self): + updated_role = self.account.update_role(2) + + assert isinstance(updated_role, Role) From 18fba50abeca02ca8e335ca1e147697807f008c4 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 18 May 2016 14:38:59 -0400 Subject: [PATCH 026/145] finished docstrings --- pycanvas/account.py | 60 +++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index ba73ffb3..84ecfdbe 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -287,13 +287,14 @@ def update(self, **kwargs): def list_roles(self): """ - List the roles available to an account + List the roles available to an account. :calls: `GET /api/v1/accounts/:account_id/roles \ + - :param account_id: The id of the account to retrieve roles + :param account_id: The ID of the account to retrieve roles for - :type account_id: string - :rtype: :class: `pycanvas.paginated_list.PaginatedList` of :class: `pycanvas.role.Role` + :type account_id: str + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.role.Role` """ from role import Role @@ -306,14 +307,16 @@ def list_roles(self): def get_role(self, role_id): """ - Retrieve information about a single role + Retrieve information about a single role. + :calls: `GET /api/v1/accounts/:account_id/roles/:id \ - https://canvas.instructure.com/doc/api/roles.html#method.role_overrides.show` + + ` :param account_id: The id of the account containing the role - :type account_id: string + :type account_id: str :param role_id: The unique identifier for the role :type role_id: int - :rtype: :class: `pycanvas.role.Role` + :rtype: :class:`pycanvas.role.Role` """ from role import Role @@ -325,12 +328,14 @@ def get_role(self, role_id): def create_role(self, label): """ - Create a new course-level or account-level role + Create a new course-level or account-level role. + :calls: `POST /api/v1/accounts/:account_id/roles \ - https://canvas.instructure.com/doc/api/roles.html#method.role_overrides.add_role` + + ` :param label: The label for the role - :type label: string - :rtype: :class: `pycanvas.role.Role` + :type label: str + :rtype: :class:`pycanvas.role.Role` """ from role import Role @@ -342,12 +347,14 @@ def create_role(self, label): def deactivate_role(self, role_id): """ - Deactivates a custom role + Deactivates a custom role. + :calls: `DELETE /api/v1/accounts/:account_id/roles/:id \ - https://canvas.instructure.com/doc/api/roles.html#method.role_overrides.remove_role` - :param role_id: The unique id for the role + + ` + :param role_id: The unique ID for the role :type role_id: int - :rtype: :class: `pycanvas.role.Role` + :rtype: :class:`pycanvas.role.Role` """ from role import Role @@ -359,12 +366,15 @@ def deactivate_role(self, role_id): def activate_role(self, role_id): """ - Reactivates an inactive role - :calls: `POST /api/v1/accounts/:account_id/roles/:id/activate \ - https://canvas.instructure.com/doc/api/roles.html#method.role_overrides.activate_role` - :param role_id: Id for the role + Reactivates an inactive role. + + :calls: `POST + /api/v1/accounts/:account_id/roles/:id/activate \ + + ` + :param role_id: ID for the role :type role_id: int - :rtype: :class: `pycanvas.role.Role` + :rtype: :class:`pycanvas.role.Role` """ from role import Role @@ -376,10 +386,12 @@ def activate_role(self, role_id): def update_role(self, role_id): """ - Updates permissions for an existing role + Updates permissions for an existing role. + :calls: `PUT /api/v1/accounts/:account_id/roles/:id \ - https://canvas.instructure.com/doc/api/roles.html#method.role_overrides.update` - :rtype: :class: `pycanvas.role.Role` + + ` + :rtype: :class:`pycanvas.role.Role` """ from role import Role From 6f155c7ec3c6fe763b80d1053fc6ca4aa5243b55 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 18 May 2016 15:10:52 -0400 Subject: [PATCH 027/145] Fixing docstrings ans other errors --- pycanvas/account.py | 13 +++++++------ tests/test_account.py | 3 +-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index da80e703..43f2f307 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -288,9 +288,10 @@ def update(self, **kwargs): def list_roles(self): """ List the roles available to an account. + :calls: `GET /api/v1/accounts/:account_id/roles \ + `_ - :param account_id: The ID of the account to retrieve roles for :type account_id: str @@ -310,8 +311,8 @@ def get_role(self, role_id): Retrieve information about a single role. :calls: `GET /api/v1/accounts/:account_id/roles/:id \ + `_ - ` :param account_id: The id of the account containing the role :type account_id: str :param role_id: The unique identifier for the role @@ -331,8 +332,8 @@ def create_role(self, label): Create a new course-level or account-level role. :calls: `POST /api/v1/accounts/:account_id/roles \ + `_ - ` :param label: The label for the role :type label: str :rtype: :class:`pycanvas.role.Role` @@ -350,8 +351,8 @@ def deactivate_role(self, role_id): Deactivates a custom role. :calls: `DELETE /api/v1/accounts/:account_id/roles/:id \ + `_ - ` :param role_id: The unique ID for the role :type role_id: int :rtype: :class:`pycanvas.role.Role` @@ -368,10 +369,10 @@ def activate_role(self, role_id): """ Reactivates an inactive role. - :calls: `POST + :calls: `POST /api/v1/accounts/:account_id/roles/:id/activate \ + `_ - ` :param role_id: ID for the role :type role_id: int :rtype: :class:`pycanvas.role.Role` diff --git a/tests/test_account.py b/tests/test_account.py index 5bd38eab..5edbb0b9 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -22,7 +22,7 @@ class TestAccount(unittest.TestCase): def setUpClass(self): requires = { 'account': [ - 'activate_role', 'close_notification', 'create' + 'activate_role', 'close_notification', 'create', 'create_course', 'create_2', 'create_notification', 'create_role', 'create_subaccount', 'create_user', 'deactivate_role', 'delete_user', 'enroll_by_id', @@ -263,4 +263,3 @@ def test_enroll_by_id(self): target_enrollment = self.account.enroll_by_id(1) assert isinstance(target_enrollment, Enrollment) - From 678a2bffe1ebb8c915910a2dcad7555e28a0e952 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 18 May 2016 15:32:42 -0400 Subject: [PATCH 028/145] mr changes --- pycanvas/role.py | 4 +++- tests/test_account.py | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pycanvas/role.py b/pycanvas/role.py index ee9f9d40..b02e4a0f 100644 --- a/pycanvas/role.py +++ b/pycanvas/role.py @@ -4,4 +4,6 @@ class Role(CanvasObject): def __str__(self): - return "" + return "id: %s" % ( + self.id + ) diff --git a/tests/test_account.py b/tests/test_account.py index 5edbb0b9..7d9d765c 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -44,7 +44,6 @@ def setUpClass(self): register_uris(settings.BASE_URL, requires, adapter) self.account = self.canvas.get_account(1) - self.role = self.account.get_role(2) self.user = self.canvas.get_user(1) # __str__() From f71545469c22c41b88126ba5c01fb2ce950c6ee2 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 18 May 2016 16:39:28 -0400 Subject: [PATCH 029/145] fixed more testing errors --- pycanvas/account.py | 2 +- tests/test_role.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/test_role.py diff --git a/pycanvas/account.py b/pycanvas/account.py index 43f2f307..ea900d4d 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -313,7 +313,7 @@ def get_role(self, role_id): :calls: `GET /api/v1/accounts/:account_id/roles/:id \ `_ - :param account_id: The id of the account containing the role + :param account_id: The id of the account containing the Role :type account_id: str :param role_id: The unique identifier for the role :type role_id: int diff --git a/tests/test_role.py b/tests/test_role.py new file mode 100644 index 00000000..1c7b9650 --- /dev/null +++ b/tests/test_role.py @@ -0,0 +1,34 @@ + +import unittest +import requests_mock +import settings + +from pycanvas import Canvas +from pycanvas.role import Role +from pycanvas.account import Account +from util import register_uris + + +class TestRole(unittest.TestCase): + """ + Tests Role methods. + """ + @classmethod + def setUpClass(self): + requires = { + 'account': ['get_by_id','get_role'], + 'generic': ['not_found'], + 'user': ['get_by_id'] + } + + adapter = requests_mock.Adapter() + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) + register_uris(settings.BASE_URL, requires, adapter) + + self.account = self.canvas.get_account(1) + self.role = self.account.get_role(2) + + # __str__() + def test__str__(self): + string = str(self.role) + assert isinstance(string, str) From 53ad8e2d97bd91ae913c803b40a4be94bf3a42a8 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 18 May 2016 16:45:14 -0400 Subject: [PATCH 030/145] more docstring changes --- pycanvas/account.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index ea900d4d..738d9929 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -292,9 +292,6 @@ def list_roles(self): :calls: `GET /api/v1/accounts/:account_id/roles \ `_ - :param account_id: The ID of the account to retrieve roles - for - :type account_id: str :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.role.Role` """ from role import Role @@ -308,14 +305,12 @@ def list_roles(self): def get_role(self, role_id): """ - Retrieve information about a single role. + Retrieve a Role by ID :calls: `GET /api/v1/accounts/:account_id/roles/:id \ `_ - :param account_id: The id of the account containing the Role - :type account_id: str - :param role_id: The unique identifier for the role + :param role_id: The unique identifier for the role. :type role_id: int :rtype: :class:`pycanvas.role.Role` """ @@ -334,7 +329,7 @@ def create_role(self, label): :calls: `POST /api/v1/accounts/:account_id/roles \ `_ - :param label: The label for the role + :param label: The label for the role. :type label: str :rtype: :class:`pycanvas.role.Role` """ @@ -348,12 +343,12 @@ def create_role(self, label): def deactivate_role(self, role_id): """ - Deactivates a custom role. + Deactivate a custom role. :calls: `DELETE /api/v1/accounts/:account_id/roles/:id \ `_ - :param role_id: The unique ID for the role + :param role_id: The unique ID for the role. :type role_id: int :rtype: :class:`pycanvas.role.Role` """ @@ -367,13 +362,13 @@ def deactivate_role(self, role_id): def activate_role(self, role_id): """ - Reactivates an inactive role. + Reactivate an inactive role. :calls: `POST /api/v1/accounts/:account_id/roles/:id/activate \ `_ - :param role_id: ID for the role + :param role_id: ID for the role. :type role_id: int :rtype: :class:`pycanvas.role.Role` """ @@ -387,7 +382,7 @@ def activate_role(self, role_id): def update_role(self, role_id): """ - Updates permissions for an existing role. + Update permissions for an existing role. :calls: `PUT /api/v1/accounts/:account_id/roles/:id \ From 0478a99372cf68e0335c84c69509773efd7c8a90 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 18 May 2016 16:46:24 -0400 Subject: [PATCH 031/145] more docstring changes --- pycanvas/account.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index 738d9929..6b2a555d 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -385,8 +385,10 @@ def update_role(self, role_id): Update permissions for an existing role. :calls: `PUT /api/v1/accounts/:account_id/roles/:id \ - ` + + :param role_id: ID for the role. + :type role_id: int :rtype: :class:`pycanvas.role.Role` """ from role import Role From dcfb947aab52e9160719ca6edecf2b1168e3f7be Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 18 May 2016 16:51:25 -0400 Subject: [PATCH 032/145] more docstring changes --- pycanvas/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index 6b2a555d..17701f38 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -305,7 +305,7 @@ def list_roles(self): def get_role(self, role_id): """ - Retrieve a Role by ID + Retrieve a role by ID. :calls: `GET /api/v1/accounts/:account_id/roles/:id \ `_ From 572dd78302835f2342011950ddc8d9c080eb58ea Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Thu, 19 May 2016 11:37:12 -0400 Subject: [PATCH 033/145] change list_roles to get_roles --- pycanvas/account.py | 2 +- tests/test_account.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index 17701f38..b7bf6160 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -285,7 +285,7 @@ def update(self, **kwargs): else: return False - def list_roles(self): + def get_roles(self): """ List the roles available to an account. diff --git a/tests/test_account.py b/tests/test_account.py index 7d9d765c..70df1e4d 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -225,8 +225,8 @@ def test_update_fail(self): success = account.update(account=update_account_dict) assert not success - def test_list_roles(self): - roles = self.account.list_roles() + def test_get_roles(self): + roles = self.account.get_roles() role_list = [role for role in roles] assert len(role_list) == 4 From a8b825335aa7d3e4c907f0e43f496da46b3c7415 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Thu, 19 May 2016 12:23:56 -0400 Subject: [PATCH 034/145] removed role.py --- pycanvas/account.py | 8 ++++++++ pycanvas/role.py | 9 --------- 2 files changed, 8 insertions(+), 9 deletions(-) delete mode 100644 pycanvas/role.py diff --git a/pycanvas/account.py b/pycanvas/account.py index 17701f38..af59880f 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -430,3 +430,11 @@ def __str__(self): # pragma: no cover self.id, self.report ) + +class Role(CanvasObject): + + def __str__(self): + return "id: %s" % ( + self.id + ) + diff --git a/pycanvas/role.py b/pycanvas/role.py deleted file mode 100644 index b02e4a0f..00000000 --- a/pycanvas/role.py +++ /dev/null @@ -1,9 +0,0 @@ -from canvas_object import CanvasObject - - -class Role(CanvasObject): - - def __str__(self): - return "id: %s" % ( - self.id - ) From 2bcd209f0e28a098660bd537623058c7621fcaf6 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Thu, 19 May 2016 13:59:43 -0400 Subject: [PATCH 035/145] fixed tests --- tests/fixtures/account.json | 31 ++++++++++++++++++++++--------- tests/test_account.py | 12 ++++++++++++ 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json index 609b7cc9..094525e0 100644 --- a/tests/fixtures/account.json +++ b/tests/fixtures/account.json @@ -375,7 +375,8 @@ "endpoint": "accounts/1/roles/2", "data": { "id": 1, - "role_id": 2 + "role": "StudentEnrollment", + "label": "Student" }, "status_code": 200 }, @@ -471,10 +472,14 @@ "endpoint": "accounts/1/roles", "data": [ { - "id": 1 + "id": 1, + "role": "StudentEnrollment", + "label": "Student" }, { - "id": 3 + "id": 3, + "role": "StudentEnrollment", + "label": "Student" } ], "headers": { @@ -487,10 +492,14 @@ "endpoint": "accounts/1/roles/?page=2&per_page=2", "data": [ { - "id": 5 + "id": 5, + "role": "StudentEnrollment", + "label": "Student" }, { - "id": 7 + "id": 7, + "role": "StudentEnrollment", + "label": "Student" } ], "status_code": 200 @@ -500,7 +509,8 @@ "endpoint": "accounts/1/roles", "data":{ "id": 1, - "label": 2 + "role": "StudentEnrollment", + "label": "Student" }, "status_code": 200 }, @@ -509,7 +519,8 @@ "endpoint": "accounts/1/roles/2", "data": { "id": 1, - "role_id": 2 + "role": "StudentEnrollment", + "label": "Student" }, "status_code": 200 }, @@ -518,7 +529,8 @@ "endpoint": "accounts/1/roles/2/activate", "data": { "id": 1, - "role_id": 2 + "role": "StudentEnrollment", + "label": "Student" }, "status_code": 200 }, @@ -527,7 +539,8 @@ "endpoint": "accounts/1/roles/2", "data": { "id": 1, - "role_id": 2 + "role": "StudentEnrollment", + "label": "Student" }, "status_code": 200 } diff --git a/tests/test_account.py b/tests/test_account.py index 70df1e4d..d9f497f3 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -231,31 +231,43 @@ def test_get_roles(self): assert len(role_list) == 4 assert isinstance(role_list[0], Role) + assert hasattr(role_list[0], 'role') + assert hasattr(role_list[0], 'label') def test_get_role(self): target_role = self.account.get_role(2) assert isinstance(target_role, Role) + assert hasattr(target_role, 'role') + assert hasattr(target_role, 'label') def test_create_role(self): new_role = self.account.create_role(1) assert isinstance(new_role, Role) + assert hasattr(new_role, 'role') + assert hasattr(new_role, 'label') def test_deactivate_role(self): old_role = self.account.deactivate_role(2) assert isinstance(old_role, Role) + assert hasattr(old_role, 'role') + assert hasattr(old_role, 'label') def test_activate_role(self): activated_role = self.account.activate_role(2) assert isinstance(activated_role, Role) + assert hasattr(activated_role, 'role') + assert hasattr(activated_role, 'label') def test_update_role(self): updated_role = self.account.update_role(2) assert isinstance(updated_role, Role) + assert hasattr(updated_role, 'role') + assert hasattr(updated_role, 'label') # enroll_by_id() def test_enroll_by_id(self): From a7a7ff3f8e531b6db93226ab70caca6e5ade58d1 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Thu, 19 May 2016 16:44:19 -0400 Subject: [PATCH 036/145] added to fixtures --- tests/fixtures/account.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json index 094525e0..3801e9eb 100644 --- a/tests/fixtures/account.json +++ b/tests/fixtures/account.json @@ -478,8 +478,8 @@ }, { "id": 3, - "role": "StudentEnrollment", - "label": "Student" + "role": "StudentEnrollment1", + "label": "Student1" } ], "headers": { @@ -493,13 +493,13 @@ "data": [ { "id": 5, - "role": "StudentEnrollment", - "label": "Student" + "role": "StudentEnrollment2", + "label": "Student2" }, { "id": 7, - "role": "StudentEnrollment", - "label": "Student" + "role": "StudentEnrollment3", + "label": "Student3" } ], "status_code": 200 From bf06741b3d126176d52362979c247f5d12c08e71 Mon Sep 17 00:00:00 2001 From: Jesse McBride Date: Mon, 23 May 2016 16:33:34 -0400 Subject: [PATCH 037/145] Tiny changes to docstrings. --- pycanvas/account.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index 650766cf..d5f678b0 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -310,7 +310,7 @@ def get_role(self, role_id): :calls: `GET /api/v1/accounts/:account_id/roles/:id \ `_ - :param role_id: The unique identifier for the role. + :param role_id: The ID of the role. :type role_id: int :rtype: :class:`pycanvas.role.Role` """ @@ -348,7 +348,7 @@ def deactivate_role(self, role_id): :calls: `DELETE /api/v1/accounts/:account_id/roles/:id \ `_ - :param role_id: The unique ID for the role. + :param role_id: The ID of the role. :type role_id: int :rtype: :class:`pycanvas.role.Role` """ @@ -364,11 +364,10 @@ def activate_role(self, role_id): """ Reactivate an inactive role. - :calls: `POST - /api/v1/accounts/:account_id/roles/:id/activate \ + :calls: `POST /api/v1/accounts/:account_id/roles/:id/activate \ `_ - :param role_id: ID for the role. + :param role_id: The ID of the role. :type role_id: int :rtype: :class:`pycanvas.role.Role` """ @@ -385,9 +384,9 @@ def update_role(self, role_id): Update permissions for an existing role. :calls: `PUT /api/v1/accounts/:account_id/roles/:id \ - ` + `_ - :param role_id: ID for the role. + :param role_id: The ID of the role. :type role_id: int :rtype: :class:`pycanvas.role.Role` """ @@ -431,10 +430,8 @@ def __str__(self): # pragma: no cover self.report ) + class Role(CanvasObject): def __str__(self): - return "id: %s" % ( - self.id - ) - + return "id: %s" % (self.id) From 1531cd60f2467c399a79ce358779e637ce9d5dc8 Mon Sep 17 00:00:00 2001 From: Jesse McBride Date: Mon, 23 May 2016 16:36:04 -0400 Subject: [PATCH 038/145] Convert spaces to tabs in account.json --- tests/fixtures/account.json | 102 ++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json index 3801e9eb..26394a6a 100644 --- a/tests/fixtures/account.json +++ b/tests/fixtures/account.json @@ -375,8 +375,8 @@ "endpoint": "accounts/1/roles/2", "data": { "id": 1, - "role": "StudentEnrollment", - "label": "Student" + "role": "StudentEnrollment", + "label": "Student" }, "status_code": 200 }, @@ -474,18 +474,18 @@ { "id": 1, "role": "StudentEnrollment", - "label": "Student" + "label": "Student" }, { "id": 3, "role": "StudentEnrollment1", - "label": "Student1" + "label": "Student1" } ], "headers": { - "Link": "; rel=\"next\"" - }, - "status_code": 200 + "Link": "; rel=\"next\"" + }, + "status_code": 200 }, "list_roles_2": { "method": "GET", @@ -494,54 +494,54 @@ { "id": 5, "role": "StudentEnrollment2", - "label": "Student2" + "label": "Student2" }, { "id": 7, "role": "StudentEnrollment3", - "label": "Student3" + "label": "Student3" } ], - "status_code": 200 - }, - "create_role": { - "method": "POST", - "endpoint": "accounts/1/roles", - "data":{ - "id": 1, - "role": "StudentEnrollment", - "label": "Student" - }, - "status_code": 200 - }, - "deactivate_role": { - "method": "DELETE", - "endpoint": "accounts/1/roles/2", - "data": { - "id": 1, - "role": "StudentEnrollment", - "label": "Student" - }, - "status_code": 200 - }, - "activate_role": { - "method": "POST", - "endpoint": "accounts/1/roles/2/activate", - "data": { - "id": 1, - "role": "StudentEnrollment", - "label": "Student" - }, - "status_code": 200 - }, - "update_role": { - "method": "PUT", - "endpoint": "accounts/1/roles/2", - "data": { - "id": 1, - "role": "StudentEnrollment", - "label": "Student" - }, - "status_code": 200 - } + "status_code": 200 + }, + "create_role": { + "method": "POST", + "endpoint": "accounts/1/roles", + "data":{ + "id": 1, + "role": "StudentEnrollment", + "label": "Student" + }, + "status_code": 200 + }, + "deactivate_role": { + "method": "DELETE", + "endpoint": "accounts/1/roles/2", + "data": { + "id": 1, + "role": "StudentEnrollment", + "label": "Student" + }, + "status_code": 200 + }, + "activate_role": { + "method": "POST", + "endpoint": "accounts/1/roles/2/activate", + "data": { + "id": 1, + "role": "StudentEnrollment", + "label": "Student" + }, + "status_code": 200 + }, + "update_role": { + "method": "PUT", + "endpoint": "accounts/1/roles/2", + "data": { + "id": 1, + "role": "StudentEnrollment", + "label": "Student" + }, + "status_code": 200 + } } From 34c15054a28a21285c40aff62596aa08c024f9d6 Mon Sep 17 00:00:00 2001 From: Jesse McBride Date: Tue, 24 May 2016 12:24:40 -0400 Subject: [PATCH 039/145] Add kwargs to methods that take optional parameters. --- pycanvas/account.py | 17 ++++++++++------- pycanvas/exceptions.py | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index f9bd97e0..3399add8 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -310,7 +310,7 @@ def update(self, **kwargs): else: return False - def get_roles(self): + def get_roles(self, **kwargs): """ List the roles available to an account. @@ -325,7 +325,8 @@ def get_roles(self): Role, self._requester, 'GET', - 'accounts/%s/roles' % (self.id) + 'accounts/%s/roles' % (self.id), + **combine_kwargs(**kwargs) ) def get_role(self, role_id): @@ -347,7 +348,7 @@ def get_role(self, role_id): ) return Role(self._requester, response.json()) - def create_role(self, label): + def create_role(self, label, **kwargs): """ Create a new course-level or account-level role. @@ -362,7 +363,8 @@ def create_role(self, label): response = self._requester.request( 'POST', - 'accounts/%s/roles' % (self.id) + 'accounts/%s/roles' % (self.id), + **combine_kwargs(**kwargs) ) return Role(self._requester, response.json()) @@ -381,7 +383,7 @@ def deactivate_role(self, role_id): response = self._requester.request( 'DELETE', - 'accounts/%s/roles/%s' % (self.id, role_id) + 'accounts/%s/roles/%s' % (self.id, role_id), ) return Role(self._requester, response.json()) @@ -404,7 +406,7 @@ def activate_role(self, role_id): ) return Role(self._requester, response.json()) - def update_role(self, role_id): + def update_role(self, role_id, **kwargs): """ Update permissions for an existing role. @@ -419,7 +421,8 @@ def update_role(self, role_id): response = self._requester.request( 'PUT', - 'accounts/%s/roles/%s' % (self.id, role_id) + 'accounts/%s/roles/%s' % (self.id, role_id), + **combine_kwargs(**kwargs) ) return Role(self._requester, response.json()) diff --git a/pycanvas/exceptions.py b/pycanvas/exceptions.py index 6d0a3a20..ce37c7c5 100644 --- a/pycanvas/exceptions.py +++ b/pycanvas/exceptions.py @@ -15,7 +15,7 @@ def __init__(self, message): if errors: self.message = str(errors) else: - self.message = 'Something went wrong.' + self.message = ('Something went wrong.', message) else: self.message = message From 8cff85df1f7317d35cb5aa04c160ff89d9867097 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Wed, 13 Jul 2016 10:59:32 -0400 Subject: [PATCH 040/145] added Conversation class, get_conversation to canvas, tests --- pycanvas/canvas.py | 18 ++++++++++++++++++ pycanvas/conversation.py | 7 +++++++ tests/fixtures/conversation.json | 11 +++++++++++ tests/test_canvas.py | 13 +++++++++++-- tests/test_conversation.py | 30 ++++++++++++++++++++++++++++++ 5 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 pycanvas/conversation.py create mode 100644 tests/fixtures/conversation.json create mode 100644 tests/test_conversation.py diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index c6a48016..3c175933 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -313,3 +313,21 @@ def search_accounts(self, **kwargs): **combine_kwargs(**kwargs) ) return response.json() + + def get_conversation(self, conversation_id, **kwargs): + """ + Return single Conversation + + :calls: `GET /api/v1/conversations/:id \ + ` + + :param conversation_id: The ID of the conversation. + :type conversation_id: int + :rtype: :class:`pycanvas.conversation.Conversation` + """ + from conversation import Conversation + response = self.__requester.request( + 'GET', + 'conversations/%s' % (conversation_id) + ) + return Conversation(self.__requester, response.json()) diff --git a/pycanvas/conversation.py b/pycanvas/conversation.py new file mode 100644 index 00000000..e542a5d4 --- /dev/null +++ b/pycanvas/conversation.py @@ -0,0 +1,7 @@ +from canvas_object import CanvasObject + + +class Conversation(CanvasObject): + + def __str__(self): + return "%s %s" % (self.id, self.subject) diff --git a/tests/fixtures/conversation.json b/tests/fixtures/conversation.json new file mode 100644 index 00000000..afe2d0ef --- /dev/null +++ b/tests/fixtures/conversation.json @@ -0,0 +1,11 @@ +{ + "get_by_id": { + "method": "GET", + "endpoint": "conversations/1", + "data": { + "id": 1, + "subject": "Pokemon Go" + }, + "status_code": 200 + } +} diff --git a/tests/test_canvas.py b/tests/test_canvas.py index 112eb9cc..f10a5fe7 100644 --- a/tests/test_canvas.py +++ b/tests/test_canvas.py @@ -6,6 +6,7 @@ import settings from pycanvas import Canvas from pycanvas.account import Account +from pycanvas.conversation import Conversation from pycanvas.course import Course, CourseNickname from pycanvas.exceptions import ResourceDoesNotExist from pycanvas.section import Section @@ -23,22 +24,23 @@ def setUpClass(self): 'account': [ 'create', 'domains', 'get_by_id', 'multiple', 'multiple_course' ], + 'conversation': ['get_by_id'], 'course': [ 'get_by_id', 'multiple', 'multiple_page_2', 'start_at_date', 'unicode_encode_error' ], - 'generic': ['not_found'], 'section': ['get_by_id'], 'user': [ 'activity_stream_summary', 'course_nickname', 'course_nickname_set', 'course_nicknames', 'course_nicknames_delete', 'course_nicknames_page_2', 'courses', 'courses_p2', 'get_by_id', 'get_by_id_type', 'todo_items', 'upcoming_events' - ], + ] } adapter = requests_mock.Adapter() self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) + register_uris(settings.BASE_URL, {'generic': ['not_found']}, adapter) register_uris(settings.BASE_URL, requires, adapter) # create_account() @@ -191,3 +193,10 @@ def test_section(self): info = self.canvas.get_section(1) assert isinstance(info, Section) + + # get_conversation() + def test_get_conversation(self): + convo = self.canvas.get_conversation(1) + + assert isinstance(convo, Conversation) + assert hasattr(convo, 'subject') diff --git a/tests/test_conversation.py b/tests/test_conversation.py new file mode 100644 index 00000000..2ded6733 --- /dev/null +++ b/tests/test_conversation.py @@ -0,0 +1,30 @@ +import unittest + +import requests_mock + +import settings +from pycanvas import Canvas +from util import register_uris + + +class TestConversation(unittest.TestCase): + """ + Tests PageView functionality. + """ + @classmethod + def setUpClass(self): + requires = { + 'conversation': ['get_by_id'] + } + + adapter = requests_mock.Adapter() + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) + register_uris(settings.BASE_URL, {'generic': ['not_found']}, adapter) + register_uris(settings.BASE_URL, requires, adapter) + + self.conversation = self.canvas.get_conversation(1) + + # __str__() + def test__str__(self): + string = str(self.conversation) + assert isinstance(string, str) From ed4c3bd764f6f8863621915e6128753c1702ba09 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Mon, 18 Jul 2016 10:26:05 -0400 Subject: [PATCH 041/145] added get_conversations() and tests, getting 404 error on tests --- pycanvas/canvas.py | 23 +++++++++++++++++++++- tests/fixtures/conversation.json | 33 ++++++++++++++++++++++++++++++++ tests/test_canvas.py | 11 ++++++++++- 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index 3c175933..6ee8a082 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -328,6 +328,27 @@ def get_conversation(self, conversation_id, **kwargs): from conversation import Conversation response = self.__requester.request( 'GET', - 'conversations/%s' % (conversation_id) + 'conversations/%s' % (conversation_id), + **combine_kwargs(**kwargs) ) return Conversation(self.__requester, response.json()) + + def get_conversations(self, **kwargs): + """ + Return list of conversations for the current user, most resent ones first. + + :calls: `GET /api/v1/conversations \ + `_ + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.conversation.Conversation` + """ + from conversation import Conversation + + return PaginatedList( + Conversation, + self.__requester, + 'GET', + 'conversations', + **combine_kwargs(**kwargs) + ) + diff --git a/tests/fixtures/conversation.json b/tests/fixtures/conversation.json index afe2d0ef..7119344c 100644 --- a/tests/fixtures/conversation.json +++ b/tests/fixtures/conversation.json @@ -7,5 +7,38 @@ "subject": "Pokemon Go" }, "status_code": 200 + }, + "get_conversations": { + "method": "GET:", + "endpoint": "conversations", + "data": [ + { + "id": 1, + "subject": "Pokemon Go" + }, + { + "id": 2, + "subject": "Agar.io" + } + ], + "status_code": 200, + "headers": { + "Link": "; rel=\"next\"" + } + }, + "get_conversations_2": { + "method": "GET", + "endpoint": "conversations?page=2&per_page=2", + "data": [ + { + "id": 3, + "subject": "Minecraft" + }, + { + "id": 4, + "subject": "Smash Bros" + } + ], + "status_code": 200 } } diff --git a/tests/test_canvas.py b/tests/test_canvas.py index f10a5fe7..9e7a3453 100644 --- a/tests/test_canvas.py +++ b/tests/test_canvas.py @@ -24,7 +24,7 @@ def setUpClass(self): 'account': [ 'create', 'domains', 'get_by_id', 'multiple', 'multiple_course' ], - 'conversation': ['get_by_id'], + 'conversation': ['get_by_id', 'get_conversations', 'get_conversations_2'], 'course': [ 'get_by_id', 'multiple', 'multiple_page_2', 'start_at_date', 'unicode_encode_error' @@ -200,3 +200,12 @@ def test_get_conversation(self): assert isinstance(convo, Conversation) assert hasattr(convo, 'subject') + + # get_conversations() + def test_get_conversations(self): + convos = self.canvas.get_conversations() + conversation_list = [conversation for conversation in convos] + + assert len(conversation_list) == 4 + assert isinstance(conversation_list[0], Conversation) + From c36613bceb383f3b4e6229520a835d4299ee168c Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Mon, 18 Jul 2016 10:42:19 -0400 Subject: [PATCH 042/145] fixed error with test --- tests/fixtures/conversation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fixtures/conversation.json b/tests/fixtures/conversation.json index 7119344c..395ed403 100644 --- a/tests/fixtures/conversation.json +++ b/tests/fixtures/conversation.json @@ -9,7 +9,7 @@ "status_code": 200 }, "get_conversations": { - "method": "GET:", + "method": "GET", "endpoint": "conversations", "data": [ { From d82a63f1b07af193026e00322a0b10c97d66b8fb Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Mon, 18 Jul 2016 15:53:26 -0400 Subject: [PATCH 043/145] added create_conversation() to canvas.py and related tests. Edited paginated_list.py to accept POST requests --- pycanvas/canvas.py | 31 ++++++++++++++++++++++++++++++- pycanvas/paginated_list.py | 3 ++- tests/fixtures/conversation.json | 15 +++++++++++++++ tests/test_canvas.py | 15 ++++++++++++++- 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index 6ee8a082..4767a4e4 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -340,7 +340,8 @@ def get_conversations(self, **kwargs): :calls: `GET /api/v1/conversations \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.conversation.Conversation` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.conversation.Conversation` """ from conversation import Conversation @@ -352,3 +353,31 @@ def get_conversations(self, **kwargs): **combine_kwargs(**kwargs) ) + def create_conversation(self, recipients, body, **kwargs): + """ + Create a new Conversation. + + :calls: `POST /api/v1/conversations \ + `_ + + :param recipients[]: An array of recipient ids. + These may be user ids or course/group ids prefixed + with 'course_' or 'group_' respectively, + e.g. recipients[]=1&recipients=2&recipients[]=course_3 + :type recipients[]: string array + :param body: The body of the conversation. + :type body: string + :rtype: :class:`pycanvas.account.Conversation` + """ + from conversation import Conversation + + + return PaginatedList( + Conversation, + self.__requester, + 'POST', + 'conversations', + recipients=recipients, + body=body, + **combine_kwargs(**kwargs) + ) diff --git a/pycanvas/paginated_list.py b/pycanvas/paginated_list.py index 8241441c..de940691 100644 --- a/pycanvas/paginated_list.py +++ b/pycanvas/paginated_list.py @@ -17,6 +17,7 @@ def __init__(self, content_class, requester, request_method, first_url, extra_at self.__next_url = first_url self.__next_params = self.__first_params self.__extra_attribs = extra_attribs or {} + self.__request_method = request_method def __getitem__(self, index): assert isinstance(index, (int, slice)) @@ -54,7 +55,7 @@ def _has_next(self): def _get_next_page(self): response = self.__requester.request( - 'GET', + self.__request_method, self.__next_url, **self.__next_params ) diff --git a/tests/fixtures/conversation.json b/tests/fixtures/conversation.json index 395ed403..2c5cf539 100644 --- a/tests/fixtures/conversation.json +++ b/tests/fixtures/conversation.json @@ -40,5 +40,20 @@ } ], "status_code": 200 + }, + "create_conversation": { + "method": "POST", + "endpoint": "conversations", + "data": [ + { + "recipients": ["1", "2"], + "body": "Test Conversation Body" + }, + { + "recipients": ["3", "4"], + "body": "Test Conversation Body 2" + } + ], + "status_code": 200 } } diff --git a/tests/test_canvas.py b/tests/test_canvas.py index 9e7a3453..358b2e61 100644 --- a/tests/test_canvas.py +++ b/tests/test_canvas.py @@ -24,7 +24,7 @@ def setUpClass(self): 'account': [ 'create', 'domains', 'get_by_id', 'multiple', 'multiple_course' ], - 'conversation': ['get_by_id', 'get_conversations', 'get_conversations_2'], + 'conversation': ['get_by_id', 'get_conversations', 'get_conversations_2', 'create_conversation'], 'course': [ 'get_by_id', 'multiple', 'multiple_page_2', 'start_at_date', 'unicode_encode_error' @@ -209,3 +209,16 @@ def test_get_conversations(self): assert len(conversation_list) == 4 assert isinstance(conversation_list[0], Conversation) + # create_conversation() + def test_create_conversation(self): + recipients = ['1', '2'] + body = 'Test Conversation Body' + + conversations = self.canvas.create_conversation(recipients=recipients, body=body) + + conversation_list = [conversation for conversation in conversations] + + print conversation_list + print len(conversation_list) + assert isinstance(conversation_list[0], Conversation) + assert len(conversation_list) == 2 From bdb8e232367777fe3e29b6256ba2673d3f1905b4 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Wed, 20 Jul 2016 11:20:36 -0400 Subject: [PATCH 044/145] added edit_conversation to canvas.py and corresponding json/test implementation --- pycanvas/canvas.py | 19 +++++ tests/fixtures/conversation.json | 123 +++++++++++++++++-------------- tests/test_canvas.py | 18 ++++- 3 files changed, 100 insertions(+), 60 deletions(-) diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index 4767a4e4..ae726a32 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -381,3 +381,22 @@ def create_conversation(self, recipients, body, **kwargs): body=body, **combine_kwargs(**kwargs) ) + + def edit_conversation(self, conversation_id, **kwargs): + """ + Update a conversation. + + :calls: `PUT /api/v1/conversations/:id \ + `_ + + :param conversation_id: The ID of the conversation. + :type conversation_id: int + :rtype: :class:`pycanvas.conversation.Conversation` + """ + from conversation import Conversation + response = self.__requester.request( + 'PUT', + 'conversations/%s' % (conversation_id), + **combine_kwargs(**kwargs) + ) + return Conversation(self.__requester, response.json()) diff --git a/tests/fixtures/conversation.json b/tests/fixtures/conversation.json index 2c5cf539..e874b4f1 100644 --- a/tests/fixtures/conversation.json +++ b/tests/fixtures/conversation.json @@ -1,59 +1,68 @@ { - "get_by_id": { - "method": "GET", - "endpoint": "conversations/1", - "data": { - "id": 1, - "subject": "Pokemon Go" - }, - "status_code": 200 - }, - "get_conversations": { - "method": "GET", - "endpoint": "conversations", - "data": [ - { - "id": 1, - "subject": "Pokemon Go" - }, - { - "id": 2, - "subject": "Agar.io" - } - ], - "status_code": 200, - "headers": { - "Link": "; rel=\"next\"" - } - }, - "get_conversations_2": { - "method": "GET", - "endpoint": "conversations?page=2&per_page=2", - "data": [ - { - "id": 3, - "subject": "Minecraft" - }, - { - "id": 4, - "subject": "Smash Bros" - } - ], - "status_code": 200 - }, - "create_conversation": { - "method": "POST", - "endpoint": "conversations", - "data": [ - { - "recipients": ["1", "2"], - "body": "Test Conversation Body" - }, - { - "recipients": ["3", "4"], - "body": "Test Conversation Body 2" - } - ], - "status_code": 200 - } + "get_by_id": { + "method": "GET", + "endpoint": "conversations/1", + "data": { + "id": 1, + "subject": "Pokemon Go" + }, + "status_code": 200 + }, + "get_conversations": { + "method": "GET", + "endpoint": "conversations", + "data": [ + { + "id": 1, + "subject": "Pokemon Go" + }, + { + "id": 2, + "subject": "Agar.io" + } + ], + "status_code": 200, + "headers": { + "Link": "; rel=\"next\"" + } + }, + "get_conversations_2": { + "method": "GET", + "endpoint": "conversations?page=2&per_page=2", + "data": [ + { + "id": 3, + "subject": "Minecraft" + }, + { + "id": 4, + "subject": "Smash Bros" + } + ], + "status_code": 200 + }, + "create_conversation": { + "method": "POST", + "endpoint": "conversations", + "data": [ + { + "recipients": ["1", "2"], + "body": "Test Conversation Body" + }, + { + "recipients": ["3", "4"], + "body": "Test Conversation Body 2" + } + ], + "status_code": 200 + }, + "edit_conversation": { + "method": "PUT", + "endpoint": "conversations/2", + "data": { + "id": 2, + "subject": "conversations api example", + "private": true + } + } } diff --git a/tests/test_canvas.py b/tests/test_canvas.py index 358b2e61..a2d05d1f 100644 --- a/tests/test_canvas.py +++ b/tests/test_canvas.py @@ -24,7 +24,10 @@ def setUpClass(self): 'account': [ 'create', 'domains', 'get_by_id', 'multiple', 'multiple_course' ], - 'conversation': ['get_by_id', 'get_conversations', 'get_conversations_2', 'create_conversation'], + 'conversation': [ + 'get_by_id', 'get_conversations', 'get_conversations_2', 'create_conversation', + 'edit_conversation' + ], 'course': [ 'get_by_id', 'multiple', 'multiple_page_2', 'start_at_date', 'unicode_encode_error' @@ -218,7 +221,16 @@ def test_create_conversation(self): conversation_list = [conversation for conversation in conversations] - print conversation_list - print len(conversation_list) assert isinstance(conversation_list[0], Conversation) assert len(conversation_list) == 2 + + def test_edit_conversation(self): + subject_string = "conversations api example" + this_id = 2 + + conversation = self.canvas.edit_conversation(conversation_id=this_id, subject=subject_string) + + assert isinstance(conversation, Conversation) + assert hasattr(conversation, 'subject') + assert conversation.subject == subject_string + assert conversation.id == this_id From 25c55746aa5397306189f0f53f432ff006ffe7c3 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Thu, 21 Jul 2016 10:57:47 -0400 Subject: [PATCH 045/145] added delete() to conversation.py. Added respective tests. Migrated edit() to conversation.py. Cleaned up files to prevent linter errors. --- pycanvas/canvas.py | 52 +++++++++++--------------------- pycanvas/conversation.py | 44 ++++++++++++++++++++++++++- tests/fixtures/conversation.json | 37 +++++++++++++++++++++-- tests/test_canvas.py | 18 ++--------- tests/test_conversation.py | 32 ++++++++++++++++++-- 5 files changed, 126 insertions(+), 57 deletions(-) diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index ae726a32..4fc7ec59 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -1,9 +1,9 @@ -from account import Account -from course import Course -from paginated_list import PaginatedList -from requester import Requester -from user import User -from util import combine_kwargs +from pycanvas.account import Account +from pycanvas.course import Course +from pycanvas.paginated_list import PaginatedList +from pycanvas.requester import Requester +from pycanvas.user import User +from pycanvas.util import combine_kwargs class Canvas(object): @@ -118,7 +118,8 @@ def get_user(self, user_id, id_type=None): Retrieve a user by their ID. `id_type` denotes which endpoint to try as there are several different IDs that can pull the same user record from Canvas. - Refer to API documentation's `User `_ + Refer to API documentation's + `User `_ example to see the ID types a user can be retrieved with. :calls: `GET /users/:id \ @@ -211,9 +212,10 @@ def get_course_nicknames(self): :calls: `GET /api/v1/users/self/course_nicknames \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.course_nickname.CourseNickname` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of \ + :class:`pycanvas.course_nickname.CourseNickname` """ - from course import CourseNickname + from pycanvas.course import CourseNickname return PaginatedList( CourseNickname, @@ -233,7 +235,7 @@ def get_course_nickname(self, course_id): :type course_id: int :rtype: :class:`pycanvas.course_nickname.CourseNickname` """ - from course import CourseNickname + from pycanvas.course import CourseNickname response = self.__requester.request( 'GET', @@ -250,7 +252,7 @@ def get_section(self, section_id): :rtype: Section """ - from section import Section + from pycanvas.section import Section response = self.__requester.request( 'GET', 'sections/%s' % (section_id) @@ -272,7 +274,7 @@ def set_course_nickname(self, course_id, nickname): :type nickname: str :rtype: :class:`pycanvas.course_nickname.CourseNickname` """ - from course import CourseNickname + from pycanvas.course import CourseNickname response = self.__requester.request( 'PUT', @@ -325,7 +327,7 @@ def get_conversation(self, conversation_id, **kwargs): :type conversation_id: int :rtype: :class:`pycanvas.conversation.Conversation` """ - from conversation import Conversation + from pycanvas.conversation import Conversation response = self.__requester.request( 'GET', 'conversations/%s' % (conversation_id), @@ -343,7 +345,7 @@ def get_conversations(self, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.conversation.Conversation` """ - from conversation import Conversation + from pycanvas.conversation import Conversation return PaginatedList( Conversation, @@ -369,8 +371,7 @@ def create_conversation(self, recipients, body, **kwargs): :type body: string :rtype: :class:`pycanvas.account.Conversation` """ - from conversation import Conversation - + from pycanvas.conversation import Conversation return PaginatedList( Conversation, @@ -381,22 +382,3 @@ def create_conversation(self, recipients, body, **kwargs): body=body, **combine_kwargs(**kwargs) ) - - def edit_conversation(self, conversation_id, **kwargs): - """ - Update a conversation. - - :calls: `PUT /api/v1/conversations/:id \ - `_ - - :param conversation_id: The ID of the conversation. - :type conversation_id: int - :rtype: :class:`pycanvas.conversation.Conversation` - """ - from conversation import Conversation - response = self.__requester.request( - 'PUT', - 'conversations/%s' % (conversation_id), - **combine_kwargs(**kwargs) - ) - return Conversation(self.__requester, response.json()) diff --git a/pycanvas/conversation.py b/pycanvas/conversation.py index e542a5d4..d169507a 100644 --- a/pycanvas/conversation.py +++ b/pycanvas/conversation.py @@ -1,7 +1,49 @@ -from canvas_object import CanvasObject +from pycanvas.canvas_object import CanvasObject +from pycanvas.util import combine_kwargs class Conversation(CanvasObject): def __str__(self): return "%s %s" % (self.id, self.subject) + + def edit(self, **kwargs): + """ + Update a conversation. + + :calls: `PUT /api/v1/conversations/:id \ + `_ + + :rtype: bool + """ + response = self._requester.request( + 'PUT', + 'conversations/%s' % (self.id), + **combine_kwargs(**kwargs) + ) + + if response.json().get('id'): + super(Conversation, self).set_attributes(response.json()) + return True + else: + return False + + def delete(self): + """ + Delete a conversation. + + :calls: `DELETE /api/v1/conversations/:id \ + `_ + + :rtype: bool + """ + response = self._requester.request( + 'DELETE', + 'conversations/%s' % (self.id) + ) + + if response.json().get('id'): + super(Conversation, self).set_attributes(response.json()) + return True + else: + return False diff --git a/tests/fixtures/conversation.json b/tests/fixtures/conversation.json index e874b4f1..3ed9f47e 100644 --- a/tests/fixtures/conversation.json +++ b/tests/fixtures/conversation.json @@ -7,6 +7,15 @@ "subject": "Pokemon Go" }, "status_code": 200 + }, + "get_by_id_2": { + "method": "GET", + "endpoint": "conversations/2", + "data": { + "id": 2, + "subject": "Pokemon Go2" + }, + "status_code": 200 }, "get_conversations": { "method": "GET", @@ -57,12 +66,34 @@ "status_code": 200 }, "edit_conversation": { + "method": "PUT", + "endpoint": "conversations/1", + "data": { + "id": 1, + "subject": "conversations api example" + }, + "status_code": 200 + }, + "edit_conversation_fail": { "method": "PUT", "endpoint": "conversations/2", "data": { - "id": 2, - "subject": "conversations api example", - "private": true + "subject": "this should fail" + } + }, + "delete_conversation": { + "method": "DELETE", + "endpoint": "conversations/1", + "data": { + "id": 1, + "subject": "conversations api example" + } + }, + "delete_conversation_fail": { + "method": "DELETE", + "endpoint": "conversations/2", + "data": { + "subject": "this should fail" } } } diff --git a/tests/test_canvas.py b/tests/test_canvas.py index a2d05d1f..65ef94c5 100644 --- a/tests/test_canvas.py +++ b/tests/test_canvas.py @@ -3,7 +3,6 @@ import requests_mock -import settings from pycanvas import Canvas from pycanvas.account import Account from pycanvas.conversation import Conversation @@ -11,7 +10,8 @@ from pycanvas.exceptions import ResourceDoesNotExist from pycanvas.section import Section from pycanvas.user import User -from util import register_uris +from tests import settings +from tests.util import register_uris class TestCanvas(unittest.TestCase): @@ -25,8 +25,7 @@ def setUpClass(self): 'create', 'domains', 'get_by_id', 'multiple', 'multiple_course' ], 'conversation': [ - 'get_by_id', 'get_conversations', 'get_conversations_2', 'create_conversation', - 'edit_conversation' + 'get_by_id', 'get_conversations', 'get_conversations_2', 'create_conversation' ], 'course': [ 'get_by_id', 'multiple', 'multiple_page_2', 'start_at_date', @@ -223,14 +222,3 @@ def test_create_conversation(self): assert isinstance(conversation_list[0], Conversation) assert len(conversation_list) == 2 - - def test_edit_conversation(self): - subject_string = "conversations api example" - this_id = 2 - - conversation = self.canvas.edit_conversation(conversation_id=this_id, subject=subject_string) - - assert isinstance(conversation, Conversation) - assert hasattr(conversation, 'subject') - assert conversation.subject == subject_string - assert conversation.id == this_id diff --git a/tests/test_conversation.py b/tests/test_conversation.py index 2ded6733..9edab8d6 100644 --- a/tests/test_conversation.py +++ b/tests/test_conversation.py @@ -2,9 +2,9 @@ import requests_mock -import settings from pycanvas import Canvas -from util import register_uris +from tests import settings +from tests.util import register_uris class TestConversation(unittest.TestCase): @@ -14,7 +14,14 @@ class TestConversation(unittest.TestCase): @classmethod def setUpClass(self): requires = { - 'conversation': ['get_by_id'] + 'conversation': [ + 'get_by_id', + "get_by_id_2", + 'edit_conversation', + 'edit_conversation_fail', + 'delete_conversation', + 'delete_conversation_fail' + ] } adapter = requests_mock.Adapter() @@ -28,3 +35,22 @@ def setUpClass(self): def test__str__(self): string = str(self.conversation) assert isinstance(string, str) + + # edit() + def test_edit(self): + new_subject = "conversations api example" + success = self.conversation.edit(subject=new_subject) + assert success + + def test_edit_fail(self): + temp_convo = self.canvas.get_conversation(2) + assert temp_convo.edit() is False + + # delete() + def test_delete(self): + success = self.conversation.delete() + assert success + + def test_delete_fail(self): + temp_convo = self.canvas.get_conversation(2) + assert temp_convo.delete() is False From 08b04404def68e3057e70fa856dd98198c0fc343 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Thu, 21 Jul 2016 13:17:35 -0400 Subject: [PATCH 046/145] began page revisions --- pycanvas/page.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pycanvas/page.py b/pycanvas/page.py index 8764a2c6..9c797f01 100644 --- a/pycanvas/page.py +++ b/pycanvas/page.py @@ -94,3 +94,12 @@ def get_parent(self): return Group(self._requester, response.json()) elif self.parent_type == 'course': return Course(self._requester, response.json()) + + +class PageRevision(CanvasObject): + + def __str__(self): + return "revision_id: %s, updated_at: %s" % ( + self.id, + self.update + ) From c54ee16a7413030580c41f97aa731dc47372f0c6 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Thu, 21 Jul 2016 13:46:05 -0400 Subject: [PATCH 047/145] Added add_recipients() to conversation.py and corresponding test/json --- pycanvas/conversation.py | 21 +++++++++++++++++++++ tests/fixtures/conversation.json | 22 +++++++++++++++++++++- tests/test_conversation.py | 16 +++++++++++++++- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/pycanvas/conversation.py b/pycanvas/conversation.py index d169507a..e262ea4d 100644 --- a/pycanvas/conversation.py +++ b/pycanvas/conversation.py @@ -47,3 +47,24 @@ def delete(self): return True else: return False + + def add_recipients(self, recipients): + """ + Add a recipient to a conversation. + + :calls: `POST /api/v1/conversations/:id/add_recipients \ + `_ + + :param recipients[]: An array of recipient ids. + These may be user ids or course/group ids prefixed + with 'course_' or 'group_' respectively, + e.g. recipients[]=1&recipients=2&recipients[]=course_3 + :type recipients[]: string array + :rtype: :class:`pycanvas.account.Conversation` + """ + response = self._requester.request( + 'POST', + 'conversations/%s/add_recipients' % (self.id), + recipients=recipients + ) + return Conversation(self._requester, response.json()) diff --git a/tests/fixtures/conversation.json b/tests/fixtures/conversation.json index 3ed9f47e..e38c4d99 100644 --- a/tests/fixtures/conversation.json +++ b/tests/fixtures/conversation.json @@ -87,7 +87,8 @@ "data": { "id": 1, "subject": "conversations api example" - } + }, + "status_code": 200 }, "delete_conversation_fail": { "method": "DELETE", @@ -95,5 +96,24 @@ "data": { "subject": "this should fail" } + }, + "add_recipients": { + "method": "POST", + "endpoint": "conversations/1/add_recipients", + "data": { + "id": 1, + "subject": "add_recipients test", + "messages": [ + { + "id": 1, + "body": "Bob was added to the conversation by Hank TA" + }, + { + "id": 2, + "body": "Joe was added to the conversation by Hank TA" + } + ] + }, + "status_code": 200 } } diff --git a/tests/test_conversation.py b/tests/test_conversation.py index 9edab8d6..b3d6e1fb 100644 --- a/tests/test_conversation.py +++ b/tests/test_conversation.py @@ -20,7 +20,8 @@ def setUpClass(self): 'edit_conversation', 'edit_conversation_fail', 'delete_conversation', - 'delete_conversation_fail' + 'delete_conversation_fail', + 'add_recipients' ] } @@ -54,3 +55,16 @@ def test_delete(self): def test_delete_fail(self): temp_convo = self.canvas.get_conversation(2) assert temp_convo.delete() is False + + # add-recipients() + def test_add_recipients(self): + recipients = {'bob': 1, 'joe': 2} + string_bob = "Bob was added to the conversation by Hank TA" + string_joe = "Joe was added to the conversation by Hank TA" + + result = self.conversation.add_recipients([recipients['bob'],recipients['joe']]) + + assert hasattr(result, 'messages') + assert len(result.messages) == 2 + assert result.messages[0]["body"] == string_bob + assert result.messages[1]["body"] == string_joe From 37acd38307d97c450ad6a0512cb753dc5643de66 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Thu, 21 Jul 2016 16:21:42 -0400 Subject: [PATCH 048/145] added method for list revisions --- pycanvas/page.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pycanvas/page.py b/pycanvas/page.py index 9c797f01..7231c6ac 100644 --- a/pycanvas/page.py +++ b/pycanvas/page.py @@ -1,6 +1,7 @@ from canvas_object import CanvasObject from util import combine_kwargs +from paginated_list import PaginatedList class Page(CanvasObject): @@ -103,3 +104,14 @@ def __str__(self): self.id, self.update ) + + def list_revisions(self): + """ + List the revisions of a page. + + :calls: `GET /api/v1/courses/:course_id/pages/:url/revisions \ + `_ + + :rtype: :class:`pycanvas.page.Page` + """ + return Paginated \ No newline at end of file From 6f2ecb95a38f7638da3f7922da283514dea3a259 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Fri, 22 Jul 2016 10:21:55 -0400 Subject: [PATCH 049/145] Added deploy procedures --- DEPLOY.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 DEPLOY.md diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 00000000..1934f908 --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,35 @@ +PyCanvas Deploy Procedures +========================== + +Pre-Flight Checklist +-------------------- + +- On branch `master` and up-to-date +- All tests pass +- 100% coverage +- `CHANGELOG` is accurate + +Packaging +--------- + +Update version number in `setup.py`. + +Run `python setup.py sdist`. This should create a file in the `dist` directory called something like `pycanvas-0.0.0.tar.gz`. + +Generate Documentation +---------------------- + +In the `docs` directory, run `make clean html`. + +**TODO:** how to publish documentation. + +Deploy +------ + +Commit the new files and the changes to `setup.py` and push. + +Create a merge request from `master` to `stable`, and merge. + +Tag the merge commit with the version number: `git tag -a v0.0.0 -m "Release version 0.0.0" abc1234` + +Push the tag: `git push origin v0.0.0` From 0e469b14c5508f308f958d20bb03b6990716c912 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Mon, 25 Jul 2016 13:58:52 -0400 Subject: [PATCH 050/145] created fixture file for pages --- tests/fixtures/page.json | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/fixtures/page.json diff --git a/tests/fixtures/page.json b/tests/fixtures/page.json new file mode 100644 index 00000000..1a08230a --- /dev/null +++ b/tests/fixtures/page.json @@ -0,0 +1,33 @@ +{ + "get_page": { + "method": "GET", + "endpoint": "courses/1/pages/my-url", + "data": { + "id": 1, + "url": "my-url", + "title": "Awesome Page" + }, + "status_code": 200 + }, + "edit": { + "method": "PUT", + "endpoint": "courses/1/pages/my-url", + "data": { + "id": 1, + "title": "New Page", + "url": "my-url" + }, + "status_code": 200 + }, + "delete_page": { + "method": "DELETE", + "endpoint": "courses/1/pages/my-url", + "data": { + "id": 1, + "title": "Page To Be Deleted", + "url": "my-url" + }, + "status_code": 200 + } + +} \ No newline at end of file From 24b0fe30089d2fbf981de3d7616e3e298ef667f1 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Mon, 25 Jul 2016 13:59:07 -0400 Subject: [PATCH 051/145] moved relevant objects into fixture file --- pycanvas/page.py | 10 ++++++++-- tests/fixtures/course.json | 30 ------------------------------ tests/test_page.py | 3 ++- 3 files changed, 10 insertions(+), 33 deletions(-) diff --git a/pycanvas/page.py b/pycanvas/page.py index 7231c6ac..40b42ff9 100644 --- a/pycanvas/page.py +++ b/pycanvas/page.py @@ -105,7 +105,7 @@ def __str__(self): self.update ) - def list_revisions(self): + def list_revisions(self, url, **kwargs): """ List the revisions of a page. @@ -114,4 +114,10 @@ def list_revisions(self): :rtype: :class:`pycanvas.page.Page` """ - return Paginated \ No newline at end of file + return PaginatedList( + PageRevision, + self._requester, + 'courses/%s/pages/%s/revisions' % (self.id, url), + 'GET', + **combine_kwargs(**kwargs) + ) diff --git a/tests/fixtures/course.json b/tests/fixtures/course.json index 5767d5d3..b0100dd9 100644 --- a/tests/fixtures/course.json +++ b/tests/fixtures/course.json @@ -645,35 +645,5 @@ "title": "Newest Page" }, "status_code": 200 - }, - "get_page": { - "method": "GET", - "endpoint": "courses/1/pages/my-url", - "data": { - "id": 1, - "url": "my-url", - "title": "Awesome Page" - }, - "status_code": 200 - }, - "edit": { - "method": "PUT", - "endpoint": "courses/1/pages/my-url", - "data": { - "id": 1, - "title": "New Page", - "url": "my-url" - }, - "status_code": 200 - }, - "delete_page": { - "method": "DELETE", - "endpoint": "courses/1/pages/my-url", - "data": { - "id": 1, - "title": "Page To Be Deleted", - "url": "my-url" - }, - "status_code": 200 } } \ No newline at end of file diff --git a/tests/test_page.py b/tests/test_page.py index cdebd74c..7f3b5bc5 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -17,9 +17,10 @@ class TestPage(unittest.TestCase): @classmethod def setUpClass(self): requires = { - 'course': ['get_by_id', 'get_page', 'edit', 'delete_page'], + 'course': ['get_by_id'], 'group': ['get_single_group', 'get_page'], 'generic': ['not_found'], + 'page': ['get_page', 'edit', 'delete_page'] } adapter = requests_mock.Adapter() self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) From 9526df64f3fbf408e8582493ca05e8b333655f91 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Mon, 25 Jul 2016 14:11:01 -0400 Subject: [PATCH 052/145] added process.py to create the Process class, used in Conversation.batch_update() --- pycanvas/process.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 pycanvas/process.py diff --git a/pycanvas/process.py b/pycanvas/process.py new file mode 100644 index 00000000..9ab77c68 --- /dev/null +++ b/pycanvas/process.py @@ -0,0 +1,6 @@ +from pycanvas.canvas_object import CanvasObject + +class Process(CanvasObject): + + def __str__(self): # pragma: no cover + return "%s: %s, %s" % (self.id, self.tag, self.workflow_state) From 545afab301e687823cc5634c62f20d5a05124bff Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Mon, 25 Jul 2016 14:13:12 -0400 Subject: [PATCH 053/145] added to conversation.py add_message(), delete_message(), mark_all_as_read(), unread_count(), get_running_batches(), and batch_update(), as well as the corresponding tests to test_conversation.py and objects to conversation.json --- pycanvas/conversation.py | 138 ++++++++++++++++++++++++++++++- tests/fixtures/conversation.json | 91 ++++++++++++++++++++ tests/test_conversation.py | 65 ++++++++++++++- 3 files changed, 289 insertions(+), 5 deletions(-) diff --git a/pycanvas/conversation.py b/pycanvas/conversation.py index e262ea4d..50e004e1 100644 --- a/pycanvas/conversation.py +++ b/pycanvas/conversation.py @@ -48,7 +48,7 @@ def delete(self): else: return False - def add_recipients(self, recipients): + def add_recipients(self, recipients): # NEEDS TESTING ON PRODUCTION """ Add a recipient to a conversation. @@ -68,3 +68,139 @@ def add_recipients(self, recipients): recipients=recipients ) return Conversation(self._requester, response.json()) + + def add_message(self, body, **kwargs): # NEEDS TESTING + """ + Add a message to a conversation. + + :calls: `POST /api/v1/conversations/:id/add_message \ + `_ + + :param body: The body of the conversation. + :type body: string + :rtype: :class:`pycanvas.account.Conversation` but with only one message, the most recent one. + """ + response = self._requester.request( + 'POST', + 'conversations/%s/add_message' % (self.id), + body=body, + **combine_kwargs(**kwargs) + ) + return Conversation(self._requester, response.json()) + + def delete_message(self, messages): # NEEDS TESTING + """ + Delete messages from this conversation. + Note that this only affects this user's view of the conversation. + If all messages are deleted, the conversation will be as well (equivalent to DELETE) by canvas + + :calls: `POST /api/v1/conversations/:id/remove_messages \ + `_ + + :param remove[]: Array of message ids to be removed. + :type remove: array of strings + :rtype: dict + """ + response = self._requester.request( + 'POST', + 'conversations/%s/remove_messages' % (self.id), + remove=messages + ) + return response.json() + + def mark_all_as_read(self): + """ + Mark all conversations as read. + :calls: `POST /api/v1/conversations/mark_all_as_read \ + `_ + + :rtype: bool + """ + response = self._requester.request( + 'POST', + 'conversations/mark_all_as_read' + ) + return response.json() == {} + + def unread_count(self): + """ + Get the number of unread conversations for the current user + + :calls: `GET /api/v1/conversations/unread_count \ + `_ + + :rtype: simple object with unread_count, example: {'unread_count': '7'} + """ + response = self._requester.request( + 'GET', + 'conversations/unread_count' + ) + + return response.json() + + def get_running_batches(self): + """ + Returns any currently running conversation batches for the current user. + Conversation batches are created when a bulk private message is sent + asynchronously. + + :calls: `GET /api/v1/conversations/batches \ + `_ + + :rtype: dict with array of batch objects - not currently a Class + """ + + response = self._requester.request( + 'GET', + 'conversations/batches' + ) + + return response.json() + + def batch_update(self, conversation_ids, event): # IN PROGRESS + """ + + :calls: `PUT /api/v1/conversations \ + `_ + + :param conversation_ids[]: List of conversations to update. Limited to 500 conversations. + :type conversation_ids: array of strings + :param event: The action to take on each conversation. + :type event: string + :rtype: json object for a Progress - currently undefined class + """ + + from pycanvas.process import Process + + ALLOWED_EVENTS = [ + 'mark_as_read', + 'mark_as_unread', + 'star', + 'unstar', + 'archive', + 'destroy' + ] + + try: + if not event in ALLOWED_EVENTS: + raise ValueError('%s is not a valid action. Please use one of the following: %s' % ( + event, + ','.join(ALLOWED_EVENTS) + )) + + if len(conversation_ids) > 500: + raise ValueError('You have requested %s updates, which exceeds the limit of 500' % ( + len(conversation_ids) + )) + + response = self._requester.request( + 'PUT', + 'conversations', + conversation_ids=conversation_ids, + event=event + ) + return_process = Process(self._requester, response.json()) + return return_process + + except ValueError as e: + return e diff --git a/tests/fixtures/conversation.json b/tests/fixtures/conversation.json index e38c4d99..d0006bcd 100644 --- a/tests/fixtures/conversation.json +++ b/tests/fixtures/conversation.json @@ -115,5 +115,96 @@ ] }, "status_code": 200 + }, + "add_message": { + "method": "POST", + "endpoint": "conversations/1/add_message", + "data": { + "id": 1, + "subject": "add_message test subject", + "workflow_state": "unread", + "messages": + [ + { + "id": 3, + "body": "add_message test body", + "author_id": 2, + "generated": false + } + ] + }, + "status_code": 200 + }, + "delete_message": { + "method": "POST", + "endpoint": "conversations/1/remove_messages", + "data": { + "id": 1, + "subject": "delete_message example", + "workflow_state": "read", + "last_message": "delete_message message", + "message_count": 1, + "properties": ["attachments"] + }, + "status_code": 200 + }, + "mark_all_as_read": { + "method": "POST", + "endpoint": "conversations/mark_all_as_read", + "data": {}, + "status_code": 200 + }, + "unread_count": { + "method": "GET", + "endpoint": "conversations/unread_count", + "data": { + "unread_count": "7" + }, + "status_code": 200 + }, + "get_running_batches": { + "method": "GET", + "endpoint": "conversations/batches", + "data":[ + { + "id": 1, + "subject": "conversations api example", + "message": + { + "id": 1, + "body": "quick reminder, no class tomorrow", + "author_id": 1 + } + }, + { + "id": 2, + "subject": "conversations api example", + "message": + { + "id": 2, + "body": "quick reminder, no class tomorrow", + "author_id": 1 + } + } + ], + "status_code": 200 + }, + "batch_update": { + "method": "PUT", + "endpoint": "conversations", + "data": { + "id": 1, + "context_id": 1, + "context_type": "Account", + "user_id": 123, + "tag": "course_batch_update", + "completion": 100, + "workflow_state": "completed", + "created_at": "2013-01-15T15:00:00Z", + "updated_at": "2013-01-15T15:04:00Z", + "message": "17 courses processed", + "url": "https://canvas.example.edu/api/v1/progress/1" + }, + "status_code": 200 } } diff --git a/tests/test_conversation.py b/tests/test_conversation.py index b3d6e1fb..bece84e6 100644 --- a/tests/test_conversation.py +++ b/tests/test_conversation.py @@ -3,6 +3,7 @@ import requests_mock from pycanvas import Canvas +from pycanvas.conversation import Conversation from tests import settings from tests.util import register_uris @@ -21,7 +22,13 @@ def setUpClass(self): 'edit_conversation_fail', 'delete_conversation', 'delete_conversation_fail', - 'add_recipients' + 'add_recipients', + 'add_message', + 'delete_message', + 'mark_all_as_read', + 'unread_count', + 'get_running_batches', + 'batch_update' ] } @@ -56,15 +63,65 @@ def test_delete_fail(self): temp_convo = self.canvas.get_conversation(2) assert temp_convo.delete() is False - # add-recipients() + # add_recipients() def test_add_recipients(self): recipients = {'bob': 1, 'joe': 2} string_bob = "Bob was added to the conversation by Hank TA" string_joe = "Joe was added to the conversation by Hank TA" - result = self.conversation.add_recipients([recipients['bob'],recipients['joe']]) - assert hasattr(result, 'messages') assert len(result.messages) == 2 assert result.messages[0]["body"] == string_bob assert result.messages[1]["body"] == string_joe + + # add_message() + def test_add_message(self): + test_string = "add_message test body" + result = self.conversation.add_message(test_string) + assert isinstance(result, Conversation) + assert len(result.messages) == 1 + assert result.messages[0]['id'] == 3 + + # delete_message() + def test_delete_message(self): + id_list = [1] + result = self.conversation.delete_message(id_list) + assert 'subject' in result + assert result['id'] == 1 + + # mark_all_as_read() + def test_mark_all_as_read(self): + result = self.conversation.mark_all_as_read() + assert result is True + + # unread_count() + def test_unread_count(self): + result = self.conversation.unread_count() + assert result['unread_count'] == "7" + + # get_running_batches() + def test_get_running_batches(self): + result = self.conversation.get_running_batches() + assert len(result) == 2 + assert 'body' in result[0]['message'] + assert result[1]['message']['author_id'] == 1 + + # batch_update() + def test_batch_update(self): + from pycanvas.process import Process + conversation_ids= [1, 2] + this_event = "mark_as_read" + result = self.conversation.batch_update(event=this_event, conversation_ids=conversation_ids) + assert isinstance(result, Process) + + def test_batch_updated_fail_on_event(self): + conversation_ids= [1, 2] + this_event = "this doesn't work" + result = self.conversation.batch_update(event=this_event, conversation_ids=conversation_ids) + assert isinstance(result, ValueError) + + def test_batch_updated_fail_on_ids(self): + conversation_ids = [None] * 501 + this_event = "mark_as_read" + result = self.conversation.batch_update(event=this_event, conversation_ids=conversation_ids) + assert isinstance(result, ValueError) From fc073dcf61a791473e2d3db759052ee6047d7599 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 27 Jul 2016 13:24:32 -0400 Subject: [PATCH 054/145] figuring out how to organize all of this --- pycanvas/page.py | 22 +++++++++++++++++++--- tests/fixtures/course.json | 10 ++++++++++ tests/fixtures/page.json | 34 +++++++++++++++++++++++++++++++++- tests/test_page.py | 11 +++++++++-- 4 files changed, 71 insertions(+), 6 deletions(-) diff --git a/pycanvas/page.py b/pycanvas/page.py index 40b42ff9..cc17bbf0 100644 --- a/pycanvas/page.py +++ b/pycanvas/page.py @@ -105,19 +105,35 @@ def __str__(self): self.update ) - def list_revisions(self, url, **kwargs): + def list_revisions(self, url, course_id, **kwargs): """ List the revisions of a page. :calls: `GET /api/v1/courses/:course_id/pages/:url/revisions \ `_ - :rtype: :class:`pycanvas.page.Page` + :rtype: :class:`pycanvas.pagerevision.PageRevision` """ + from course import Course + return PaginatedList( PageRevision, self._requester, - 'courses/%s/pages/%s/revisions' % (self.id, url), + 'courses/%s/pages/%s/revisions' % (course_id, url), 'GET', **combine_kwargs(**kwargs) ) + + def show_latest_revision(self): + """ + Retrieve the contents of the latest revision. + + :calls: `GET /api/v1/courses/:course_id/pages/:url/revisions/latest \ + `_ + + :rtype: :class:`pycanvas.pagerevision.PageRevision` + """ + response = self._requester.request( + 'GET', + 'courses/%s/' + ) diff --git a/tests/fixtures/course.json b/tests/fixtures/course.json index b0100dd9..e79dc1fd 100644 --- a/tests/fixtures/course.json +++ b/tests/fixtures/course.json @@ -645,5 +645,15 @@ "title": "Newest Page" }, "status_code": 200 + }, + "get_page": { + "method": "GET", + "endpoint": "courses/1/pages/my-url", + "data": { + "id": 1, + "url": "my-url", + "title": "Awesome Page" + }, + "status_code": 200 } } \ No newline at end of file diff --git a/tests/fixtures/page.json b/tests/fixtures/page.json index 1a08230a..92b93a88 100644 --- a/tests/fixtures/page.json +++ b/tests/fixtures/page.json @@ -28,6 +28,38 @@ "url": "my-url" }, "status_code": 200 + }, + "list_revisions": { + "method": "GET", + "endpoint": "courses/1/pages/1/revisions", + "data": [ + { + "id": 1, + "title": "Revision 1" + }, + { + "id": 2, + "title": "Revision 2" + } + ], + "status_code": 200, + "headers": { + "Link": "; rel=\"next\"" + } + }, + "list_revisions2": { + "method": "GET", + "endpoint": "courses/1/pages/1/revisions?page=2&per_page=2", + "data": [ + { + "id": 3, + "title": "Revision 3" + }, + { + "id": 4, + "title": "Revision 4" + } + ], + "status_code": 200 } - } \ No newline at end of file diff --git a/tests/test_page.py b/tests/test_page.py index 7f3b5bc5..4e43220f 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -7,7 +7,7 @@ from pycanvas.canvas import Canvas from pycanvas.course import Course from pycanvas.group import Group -from pycanvas.page import Page +from pycanvas.page import Page, PageRevision class TestPage(unittest.TestCase): @@ -20,7 +20,7 @@ def setUpClass(self): 'course': ['get_by_id'], 'group': ['get_single_group', 'get_page'], 'generic': ['not_found'], - 'page': ['get_page', 'edit', 'delete_page'] + 'page': ['get_page', 'edit', 'delete_page', 'list_revisions', 'list_revisions2'] } adapter = requests_mock.Adapter() self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) @@ -53,6 +53,13 @@ def test_delete(self): assert isinstance(deleted_page, Page) + def test_list_revisions(self): + revisions = self.page_course.list_revisions() + rev_list = [rev for rev in revisions] + + assert len(rev_list) == 4 + assert isinstance(rev_list[0], PageRevision) + # parent_id def test_parent_id_course(self): assert self.page_course.parent_id == 1 From 7b286d29728657328fb20c4e6e8bef27e131d089 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 27 Jul 2016 16:07:17 -0400 Subject: [PATCH 055/145] added sections class --- pycanvas/course.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pycanvas/course.py b/pycanvas/course.py index bb9abd74..cabd43d0 100644 --- a/pycanvas/course.py +++ b/pycanvas/course.py @@ -708,3 +708,12 @@ def remove(self): 'users/self/course_nicknames/%s' % (self.course_id) ) return CourseNickname(self._requester, response.json()) + + +class CourseSection(CanvasObject): + + def __str__(self): + return "id: %s, name: %s: " % ( + self.id, + self.name + ) From 9e1d15a2409a81c5a57db2ca389d2e72992d14c8 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Thu, 28 Jul 2016 11:59:31 -0400 Subject: [PATCH 056/145] added methods for show revision --- pycanvas/page.py | 93 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 79 insertions(+), 14 deletions(-) diff --git a/pycanvas/page.py b/pycanvas/page.py index cc17bbf0..33db8977 100644 --- a/pycanvas/page.py +++ b/pycanvas/page.py @@ -96,16 +96,40 @@ def get_parent(self): elif self.parent_type == 'course': return Course(self._requester, response.json()) + def show_latest_revision(self): + """ + Retrieve the contents of the latest revision. -class PageRevision(CanvasObject): + :calls: `GET /api/v1/courses/:course_id/pages/:url/revisions/latest \ + `_ - def __str__(self): - return "revision_id: %s, updated_at: %s" % ( - self.id, - self.update + :rtype: :class:`pycanvas.pagerevision.PageRevision` + """ + response = self._requester.request( + 'GET', + '%ss/%s/pages/%s/revisions/latest' % (self.parent_type, self.parent_id, self.url), + ) + return PageRevision(self._requester, page_json) + + def get_revision_by_id(self, revision_id): + """ + Retrieve the contents of the revision by the id. + + :calls: `GET /api/v1/courses/:course_id/pages/:url/revisions/:revision_id \ + + + :param revision_id: The id of a specified revision. + :type revision_id: int + :returns: Contents of the page revision. + :rtype: :class: `pycanvas.pagerevision.PageRevision` + """ + response = self._requester.request( + 'GET', + '%ss/%s/pages/%s/revisions/%s' % (self.parent_type, self.parent_id, self.url, revision_id), ) + return PageRevision(self._requester, page_json) - def list_revisions(self, url, course_id, **kwargs): + def list_revisions(self, **kwargs): """ List the revisions of a page. @@ -114,26 +138,67 @@ def list_revisions(self, url, course_id, **kwargs): :rtype: :class:`pycanvas.pagerevision.PageRevision` """ - from course import Course return PaginatedList( PageRevision, self._requester, - 'courses/%s/pages/%s/revisions' % (course_id, url), 'GET', + '%ss/%s/pages/%s/revisions' % (self.parent_type, self.parent_id, self.url), **combine_kwargs(**kwargs) ) - def show_latest_revision(self): + +class PageRevision(CanvasObject): + + def __str__(self): + return "revision_id: %s, updated_at: %s" % ( + self.id, + self.update + ) + + @property + def parent_id(self): """ - Retrieve the contents of the latest revision. + Return the id of the course or group that spawned this page. - :calls: `GET /api/v1/courses/:course_id/pages/:url/revisions/latest \ - `_ + :rtype: int + """ + if hasattr(self, 'course_id'): + return self.course_id + elif hasattr(self, 'group_id'): + return self.group_id + else: + raise ValueError("Page does not have a course_id or group_id") - :rtype: :class:`pycanvas.pagerevision.PageRevision` + @property + def parent_type(self): + """ + Return whether the page was spawned from a course or group. + + :rtype: str + """ + if hasattr(self, 'course_id'): + return 'course' + elif hasattr(self, 'group_id'): + return 'group' + else: + raise ValueError("ExternalTool does not have a course_id or group_id") + + def get_parent(self): + """ + Return the object that spawned this page. + + :rtype: :class:`pycanvas.group.Group` or :class:`pycanvas.course.Course` """ + from group import Group + from course import Course + response = self._requester.request( 'GET', - 'courses/%s/' + '%ss/%s' % (self.parent_type, self.parent_id) ) + + if self.parent_type == 'group': + return Group(self._requester, response.json()) + elif self.parent_type == 'course': + return Course(self._requester, response.json()) From 3d72539c70a7ff3cc38a725279f1700a0fb7fb15 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Thu, 28 Jul 2016 12:48:14 -0400 Subject: [PATCH 057/145] Added list revisions test --- tests/test_page.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_page.py b/tests/test_page.py index 4e43220f..48745e2d 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -60,6 +60,11 @@ def test_list_revisions(self): assert len(rev_list) == 4 assert isinstance(rev_list[0], PageRevision) + def test_show_latest_revision(self): + revision = self.page_course.show_latest_revision(1) + + assert isinstance(revision, PageRevision) + # parent_id def test_parent_id_course(self): assert self.page_course.parent_id == 1 From f7f1eae73ec0e50a6fb93a2d003e595f82886773 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Thu, 28 Jul 2016 13:44:47 -0400 Subject: [PATCH 058/145] moved mark_all_as_read(), unread_count(), get_running_batches(), and batch_update() to canvas.py since they do not reference a specific conversation --- pycanvas/canvas.py | 139 +++++++++++++++++++++++++++++++------ pycanvas/conversation.py | 107 ++-------------------------- tests/test_canvas.py | 62 +++++++++++++---- tests/test_conversation.py | 43 +----------- 4 files changed, 174 insertions(+), 177 deletions(-) diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index 4fc7ec59..e8ec0290 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -316,6 +316,34 @@ def search_accounts(self, **kwargs): ) return response.json() + def create_conversation(self, recipients, body, **kwargs): + """ + Create a new Conversation. + + :calls: `POST /api/v1/conversations \ + `_ + + :param recipients[]: An array of recipient ids. + These may be user ids or course/group ids prefixed + with 'course_' or 'group_' respectively, + e.g. recipients[]=1&recipients=2&recipients[]=course_3 + :type recipients[]: string array + :param body: The body of the message being added. + :type body: string + :rtype: :class:`pycanvas.account.Conversation` + """ + from pycanvas.conversation import Conversation + + return PaginatedList( + Conversation, + self.__requester, + 'POST', + 'conversations', + recipients=recipients, + body=body, + **combine_kwargs(**kwargs) + ) + def get_conversation(self, conversation_id, **kwargs): """ Return single Conversation @@ -355,30 +383,99 @@ def get_conversations(self, **kwargs): **combine_kwargs(**kwargs) ) - def create_conversation(self, recipients, body, **kwargs): + def mark_all_as_read(self): """ - Create a new Conversation. + Mark all conversations as read. + :calls: `POST /api/v1/conversations/mark_all_as_read \ + `_ + + :rtype: bool + """ + response = self.__requester.request( + 'POST', + 'conversations/mark_all_as_read' + ) + return response.json() == {} - :calls: `POST /api/v1/conversations \ - `_ + def unread_count(self): + """ + Get the number of unread conversations for the current user - :param recipients[]: An array of recipient ids. - These may be user ids or course/group ids prefixed - with 'course_' or 'group_' respectively, - e.g. recipients[]=1&recipients=2&recipients[]=course_3 - :type recipients[]: string array - :param body: The body of the conversation. - :type body: string - :rtype: :class:`pycanvas.account.Conversation` + :calls: `GET /api/v1/conversations/unread_count \ + `_ + + :rtype: simple object with unread_count, example: {'unread_count': '7'} """ - from pycanvas.conversation import Conversation + response = self.__requester.request( + 'GET', + 'conversations/unread_count' + ) - return PaginatedList( - Conversation, - self.__requester, - 'POST', - 'conversations', - recipients=recipients, - body=body, - **combine_kwargs(**kwargs) + return response.json() + + def get_running_batches(self): + """ + Returns any currently running conversation batches for the current user. + Conversation batches are created when a bulk private message is sent + asynchronously. + + :calls: `GET /api/v1/conversations/batches \ + `_ + + :rtype: dict with list of batch objects - not currently a Class + """ + + response = self.__requester.request( + 'GET', + 'conversations/batches' ) + + return response.json() + + def batch_update(self, conversation_ids, event): # IN PROGRESS + """ + + :calls: `PUT /api/v1/conversations \ + `_ + + :param conversation_ids[]: List of conversations to update. Limited to 500 conversations. + :type conversation_ids: list of strings + :param event: The action to take on each conversation. + :type event: string + :rtype: json object for a Progress - currently undefined class + """ + + from pycanvas.process import Process + + ALLOWED_EVENTS = [ + 'mark_as_read', + 'mark_as_unread', + 'star', + 'unstar', + 'archive', + 'destroy' + ] + + try: + if not event in ALLOWED_EVENTS: + raise ValueError('%s is not a valid action. Please use one of the following: %s' % ( + event, + ','.join(ALLOWED_EVENTS) + )) + + if len(conversation_ids) > 500: + raise ValueError('You have requested %s updates, which exceeds the limit of 500' % ( + len(conversation_ids) + )) + + response = self.__requester.request( + 'PUT', + 'conversations', + conversation_ids=conversation_ids, + event=event + ) + return_process = Process(self.__requester, response.json()) + return return_process + + except ValueError as e: + return e diff --git a/pycanvas/conversation.py b/pycanvas/conversation.py index 50e004e1..e3a6d535 100644 --- a/pycanvas/conversation.py +++ b/pycanvas/conversation.py @@ -55,11 +55,11 @@ def add_recipients(self, recipients): # NEEDS TESTING ON PRODUCTION :calls: `POST /api/v1/conversations/:id/add_recipients \ `_ - :param recipients[]: An array of recipient ids. + :param recipients[]: A list of recipient ids. These may be user ids or course/group ids prefixed with 'course_' or 'group_' respectively, e.g. recipients[]=1&recipients=2&recipients[]=course_3 - :type recipients[]: string array + :type recipients[]: string list :rtype: :class:`pycanvas.account.Conversation` """ response = self._requester.request( @@ -88,7 +88,7 @@ def add_message(self, body, **kwargs): # NEEDS TESTING ) return Conversation(self._requester, response.json()) - def delete_message(self, messages): # NEEDS TESTING + def delete_message(self, remove): # NEEDS TESTING """ Delete messages from this conversation. Note that this only affects this user's view of the conversation. @@ -98,109 +98,12 @@ def delete_message(self, messages): # NEEDS TESTING `_ :param remove[]: Array of message ids to be removed. - :type remove: array of strings + :type remove: list of strings :rtype: dict """ response = self._requester.request( 'POST', 'conversations/%s/remove_messages' % (self.id), - remove=messages + remove=remove ) return response.json() - - def mark_all_as_read(self): - """ - Mark all conversations as read. - :calls: `POST /api/v1/conversations/mark_all_as_read \ - `_ - - :rtype: bool - """ - response = self._requester.request( - 'POST', - 'conversations/mark_all_as_read' - ) - return response.json() == {} - - def unread_count(self): - """ - Get the number of unread conversations for the current user - - :calls: `GET /api/v1/conversations/unread_count \ - `_ - - :rtype: simple object with unread_count, example: {'unread_count': '7'} - """ - response = self._requester.request( - 'GET', - 'conversations/unread_count' - ) - - return response.json() - - def get_running_batches(self): - """ - Returns any currently running conversation batches for the current user. - Conversation batches are created when a bulk private message is sent - asynchronously. - - :calls: `GET /api/v1/conversations/batches \ - `_ - - :rtype: dict with array of batch objects - not currently a Class - """ - - response = self._requester.request( - 'GET', - 'conversations/batches' - ) - - return response.json() - - def batch_update(self, conversation_ids, event): # IN PROGRESS - """ - - :calls: `PUT /api/v1/conversations \ - `_ - - :param conversation_ids[]: List of conversations to update. Limited to 500 conversations. - :type conversation_ids: array of strings - :param event: The action to take on each conversation. - :type event: string - :rtype: json object for a Progress - currently undefined class - """ - - from pycanvas.process import Process - - ALLOWED_EVENTS = [ - 'mark_as_read', - 'mark_as_unread', - 'star', - 'unstar', - 'archive', - 'destroy' - ] - - try: - if not event in ALLOWED_EVENTS: - raise ValueError('%s is not a valid action. Please use one of the following: %s' % ( - event, - ','.join(ALLOWED_EVENTS) - )) - - if len(conversation_ids) > 500: - raise ValueError('You have requested %s updates, which exceeds the limit of 500' % ( - len(conversation_ids) - )) - - response = self._requester.request( - 'PUT', - 'conversations', - conversation_ids=conversation_ids, - event=event - ) - return_process = Process(self._requester, response.json()) - return return_process - - except ValueError as e: - return e diff --git a/tests/test_canvas.py b/tests/test_canvas.py index 65ef94c5..af1411ee 100644 --- a/tests/test_canvas.py +++ b/tests/test_canvas.py @@ -25,7 +25,9 @@ def setUpClass(self): 'create', 'domains', 'get_by_id', 'multiple', 'multiple_course' ], 'conversation': [ - 'get_by_id', 'get_conversations', 'get_conversations_2', 'create_conversation' + 'get_by_id', 'get_conversations', 'get_conversations_2', + 'create_conversation', 'mark_all_as_read', 'unread_count', + 'get_running_batches', 'batch_update' ], 'course': [ 'get_by_id', 'multiple', 'multiple_page_2', 'start_at_date', @@ -196,6 +198,17 @@ def test_section(self): assert isinstance(info, Section) + # create_conversation() + def test_create_conversation(self): + recipients = ['1', '2'] + body = 'Test Conversation Body' + + conversations = self.canvas.create_conversation(recipients=recipients, body=body) + conversation_list = [conversation for conversation in conversations] + + assert isinstance(conversation_list[0], Conversation) + assert len(conversation_list) == 2 + # get_conversation() def test_get_conversation(self): convo = self.canvas.get_conversation(1) @@ -211,14 +224,39 @@ def test_get_conversations(self): assert len(conversation_list) == 4 assert isinstance(conversation_list[0], Conversation) - # create_conversation() - def test_create_conversation(self): - recipients = ['1', '2'] - body = 'Test Conversation Body' - - conversations = self.canvas.create_conversation(recipients=recipients, body=body) - - conversation_list = [conversation for conversation in conversations] - - assert isinstance(conversation_list[0], Conversation) - assert len(conversation_list) == 2 + # mark_all_as_read() + def test_mark_all_as_read(self): + result = self.canvas.mark_all_as_read() + assert result is True + + # unread_count() + def test_unread_count(self): + result = self.canvas.unread_count() + assert result['unread_count'] == "7" + + # get_running_batches() + def test_get_running_batches(self): + result = self.canvas.get_running_batches() + assert len(result) == 2 + assert 'body' in result[0]['message'] + assert result[1]['message']['author_id'] == 1 + + # batch_update() + def test_batch_update(self): + from pycanvas.process import Process + conversation_ids= [1, 2] + this_event = "mark_as_read" + result = self.canvas.batch_update(event=this_event, conversation_ids=conversation_ids) + assert isinstance(result, Process) + + def test_batch_updated_fail_on_event(self): + conversation_ids= [1, 2] + this_event = "this doesn't work" + result = self.canvas.batch_update(event=this_event, conversation_ids=conversation_ids) + assert isinstance(result, ValueError) + + def test_batch_updated_fail_on_ids(self): + conversation_ids = [None] * 501 + this_event = "mark_as_read" + result = self.canvas.batch_update(event=this_event, conversation_ids=conversation_ids) + assert isinstance(result, ValueError) diff --git a/tests/test_conversation.py b/tests/test_conversation.py index bece84e6..6557a8b9 100644 --- a/tests/test_conversation.py +++ b/tests/test_conversation.py @@ -24,11 +24,7 @@ def setUpClass(self): 'delete_conversation_fail', 'add_recipients', 'add_message', - 'delete_message', - 'mark_all_as_read', - 'unread_count', - 'get_running_batches', - 'batch_update' + 'delete_message' ] } @@ -88,40 +84,3 @@ def test_delete_message(self): result = self.conversation.delete_message(id_list) assert 'subject' in result assert result['id'] == 1 - - # mark_all_as_read() - def test_mark_all_as_read(self): - result = self.conversation.mark_all_as_read() - assert result is True - - # unread_count() - def test_unread_count(self): - result = self.conversation.unread_count() - assert result['unread_count'] == "7" - - # get_running_batches() - def test_get_running_batches(self): - result = self.conversation.get_running_batches() - assert len(result) == 2 - assert 'body' in result[0]['message'] - assert result[1]['message']['author_id'] == 1 - - # batch_update() - def test_batch_update(self): - from pycanvas.process import Process - conversation_ids= [1, 2] - this_event = "mark_as_read" - result = self.conversation.batch_update(event=this_event, conversation_ids=conversation_ids) - assert isinstance(result, Process) - - def test_batch_updated_fail_on_event(self): - conversation_ids= [1, 2] - this_event = "this doesn't work" - result = self.conversation.batch_update(event=this_event, conversation_ids=conversation_ids) - assert isinstance(result, ValueError) - - def test_batch_updated_fail_on_ids(self): - conversation_ids = [None] * 501 - this_event = "mark_as_read" - result = self.conversation.batch_update(event=this_event, conversation_ids=conversation_ids) - assert isinstance(result, ValueError) From f32f579b28dec22b10d30e0bcb64fe9896189465 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Thu, 28 Jul 2016 14:25:59 -0400 Subject: [PATCH 059/145] added fixture for latest revision --- tests/fixtures/page.json | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/fixtures/page.json b/tests/fixtures/page.json index 92b93a88..1b8f6429 100644 --- a/tests/fixtures/page.json +++ b/tests/fixtures/page.json @@ -31,7 +31,7 @@ }, "list_revisions": { "method": "GET", - "endpoint": "courses/1/pages/1/revisions", + "endpoint": "courses/1/pages/my-url/revisions", "data": [ { "id": 1, @@ -44,12 +44,12 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "list_revisions2": { "method": "GET", - "endpoint": "courses/1/pages/1/revisions?page=2&per_page=2", + "endpoint": "courses/1/pages/my-url/revisions?page=2&per_page=2", "data": [ { "id": 3, @@ -61,5 +61,15 @@ } ], "status_code": 200 + }, + "latest_revision": { + "method": "GET", + "endpoint": "courses/1/pages/my-url/revisions/latest", + "data": { + "id": 1, + "title": "Latest Revision", + "url": "my-url" + }, + "status_code": 200 } } \ No newline at end of file From 6cbb343a88bb5cf692dcf93d2d3e67e959a0c8cb Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Thu, 28 Jul 2016 14:27:21 -0400 Subject: [PATCH 060/145] added fixture for get latest revision --- tests/fixtures/page.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/fixtures/page.json b/tests/fixtures/page.json index 1b8f6429..ce4fea4c 100644 --- a/tests/fixtures/page.json +++ b/tests/fixtures/page.json @@ -71,5 +71,15 @@ "url": "my-url" }, "status_code": 200 + }, + "get_latest_rev_by_id": { + "method": "GET", + "endpoint": "courses/1/pages/my-url/revisions/2", + "data": { + "id": 1, + "revision_id": 2, + "url": "my-url" + }, + "status_code": 200 } } \ No newline at end of file From 7f9fcd6e97c421a8a0ea591ca5cd8e3bcc6d6851 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Thu, 28 Jul 2016 14:28:39 -0400 Subject: [PATCH 061/145] added correct fixtures --- tests/test_page.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_page.py b/tests/test_page.py index 48745e2d..5eba47d0 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -20,7 +20,7 @@ def setUpClass(self): 'course': ['get_by_id'], 'group': ['get_single_group', 'get_page'], 'generic': ['not_found'], - 'page': ['get_page', 'edit', 'delete_page', 'list_revisions', 'list_revisions2'] + 'page': ['get_page', 'edit', 'delete_page', 'list_revisions', 'list_revisions2', 'latest_revision', 'get_latest_rev_by_id'] } adapter = requests_mock.Adapter() self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) @@ -61,7 +61,12 @@ def test_list_revisions(self): assert isinstance(rev_list[0], PageRevision) def test_show_latest_revision(self): - revision = self.page_course.show_latest_revision(1) + revision = self.page_course.show_latest_revision() + + assert isinstance(revision, PageRevision) + + def test_get_revision_by_id(self): + revision = self.page_course.get_revision_by_id(1) assert isinstance(revision, PageRevision) From 823edbfa097caf632328825527028cf5124b41fb Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Thu, 28 Jul 2016 14:30:59 -0400 Subject: [PATCH 062/145] small change so tests would run --- tests/test_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_page.py b/tests/test_page.py index 5eba47d0..6ed12f74 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -66,7 +66,7 @@ def test_show_latest_revision(self): assert isinstance(revision, PageRevision) def test_get_revision_by_id(self): - revision = self.page_course.get_revision_by_id(1) + revision = self.page_course.get_revision_by_id(2) assert isinstance(revision, PageRevision) From 24f550ef7c85a702e7a84214767428d9c5e61920 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Thu, 28 Jul 2016 15:40:19 -0400 Subject: [PATCH 063/145] finished tests --- pycanvas/page.py | 24 +++++++++++++++++++++--- tests/fixtures/page.json | 10 ++++++++++ tests/test_page.py | 12 +++++++++++- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/pycanvas/page.py b/pycanvas/page.py index 33db8977..11468bf3 100644 --- a/pycanvas/page.py +++ b/pycanvas/page.py @@ -109,7 +109,7 @@ def show_latest_revision(self): 'GET', '%ss/%s/pages/%s/revisions/latest' % (self.parent_type, self.parent_id, self.url), ) - return PageRevision(self._requester, page_json) + return PageRevision(self._requester, response.json()) def get_revision_by_id(self, revision_id): """ @@ -127,7 +127,7 @@ def get_revision_by_id(self, revision_id): 'GET', '%ss/%s/pages/%s/revisions/%s' % (self.parent_type, self.parent_id, self.url, revision_id), ) - return PageRevision(self._requester, page_json) + return PageRevision(self._requester, response.json()) def list_revisions(self, **kwargs): """ @@ -138,7 +138,6 @@ def list_revisions(self, **kwargs): :rtype: :class:`pycanvas.pagerevision.PageRevision` """ - return PaginatedList( PageRevision, self._requester, @@ -147,6 +146,25 @@ def list_revisions(self, **kwargs): **combine_kwargs(**kwargs) ) + def revert_to_revision(self, revision_id): + """ + Revert the page back to a specified revision. + + :calls: `POST /api/v1/courses/:course_id/pages/:url/revisions/:revision_id \ + `_ + + :param revision_id: The id of a specified revision. + :type revision_id: int + :returns: Contents of the page revision. + :rtype: :class: `pycanvas.pagerevision.PageRevision` + """ + response = self._requester.request( + 'POST', + '%ss/%s/pages/%s/revisions/%s' % (self.parent_type, self.parent_id, self.url, revision_id), + ) + return PageRevision(self._requester, response.json()) + + class PageRevision(CanvasObject): diff --git a/tests/fixtures/page.json b/tests/fixtures/page.json index ce4fea4c..ae6b3ea2 100644 --- a/tests/fixtures/page.json +++ b/tests/fixtures/page.json @@ -81,5 +81,15 @@ "url": "my-url" }, "status_code": 200 + }, + "revert_to_revision": { + "method": "POST", + "endpoint": "courses/1/pages/my-url/revisions/3", + "data": { + "id": 1, + "revision_id": 3, + "url": "my-url" + }, + "status_code": 200 } } \ No newline at end of file diff --git a/tests/test_page.py b/tests/test_page.py index 6ed12f74..f981814e 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -20,7 +20,12 @@ def setUpClass(self): 'course': ['get_by_id'], 'group': ['get_single_group', 'get_page'], 'generic': ['not_found'], - 'page': ['get_page', 'edit', 'delete_page', 'list_revisions', 'list_revisions2', 'latest_revision', 'get_latest_rev_by_id'] + 'page': [ + 'get_page', 'edit', 'delete_page', + 'list_revisions', 'list_revisions2', + 'latest_revision', 'get_latest_rev_by_id', + 'revert_to_revision' + ] } adapter = requests_mock.Adapter() self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) @@ -70,6 +75,11 @@ def test_get_revision_by_id(self): assert isinstance(revision, PageRevision) + def test_revert_to_revision(self): + revision = self.page_course.revert_to_revision(3) + + assert isinstance(revision, PageRevision) + # parent_id def test_parent_id_course(self): assert self.page_course.parent_id == 1 From af4208cb348f683740afdfe37d794104874984fc Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Fri, 29 Jul 2016 10:46:55 -0400 Subject: [PATCH 064/145] fixed batch_update, completed tests --- pycanvas/canvas.py | 4 ++-- pycanvas/requester.py | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index e8ec0290..dbf73a5e 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -471,8 +471,8 @@ def batch_update(self, conversation_ids, event): # IN PROGRESS response = self.__requester.request( 'PUT', 'conversations', - conversation_ids=conversation_ids, - event=event + event=event, + **{"conversation_ids[]":conversation_ids} ) return_process = Process(self.__requester, response.json()) return return_process diff --git a/pycanvas/requester.py b/pycanvas/requester.py index 988356e4..a149a8e6 100644 --- a/pycanvas/requester.py +++ b/pycanvas/requester.py @@ -113,4 +113,13 @@ def _put_request(self, url, headers, data={}): :param params: dict :param data: dict """ + # import logging + # from httplib import HTTPConnection + # HTTPConnection.debuglevel = 1 + # logging.basicConfig() + # logging.getLogger().setLevel(logging.DEBUG) + # requests_log = logging.getLogger("requests.packages.urllib3") + # requests_log.setLevel(logging.DEBUG) + # requests_log.propogate = True + return self._session.put(url, headers=headers, data=data) From 1da167f3c55707d747d8ca6a9eadc36803f8fe28 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Fri, 29 Jul 2016 10:50:40 -0400 Subject: [PATCH 065/145] changed titles of conversation methods in canvas.py --- pycanvas/canvas.py | 8 ++++---- tests/test_canvas.py | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index dbf73a5e..eb61adcc 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -383,7 +383,7 @@ def get_conversations(self, **kwargs): **combine_kwargs(**kwargs) ) - def mark_all_as_read(self): + def conversations_mark_all_as_read(self): """ Mark all conversations as read. :calls: `POST /api/v1/conversations/mark_all_as_read \ @@ -397,7 +397,7 @@ def mark_all_as_read(self): ) return response.json() == {} - def unread_count(self): + def conversations_unread_count(self): """ Get the number of unread conversations for the current user @@ -413,7 +413,7 @@ def unread_count(self): return response.json() - def get_running_batches(self): + def conversations_get_running_batches(self): """ Returns any currently running conversation batches for the current user. Conversation batches are created when a bulk private message is sent @@ -432,7 +432,7 @@ def get_running_batches(self): return response.json() - def batch_update(self, conversation_ids, event): # IN PROGRESS + def conversations_batch_update(self, conversation_ids, event): # IN PROGRESS """ :calls: `PUT /api/v1/conversations \ diff --git a/tests/test_canvas.py b/tests/test_canvas.py index af1411ee..5cf1a716 100644 --- a/tests/test_canvas.py +++ b/tests/test_canvas.py @@ -225,38 +225,38 @@ def test_get_conversations(self): assert isinstance(conversation_list[0], Conversation) # mark_all_as_read() - def test_mark_all_as_read(self): - result = self.canvas.mark_all_as_read() + def test_conversations_mark_all_as_read(self): + result = self.canvas.conversations_mark_all_as_read() assert result is True # unread_count() - def test_unread_count(self): - result = self.canvas.unread_count() + def test_conversations_unread_count(self): + result = self.canvas.conversations_unread_count() assert result['unread_count'] == "7" # get_running_batches() - def test_get_running_batches(self): - result = self.canvas.get_running_batches() + def test_conversations_get_running_batches(self): + result = self.canvas.conversations_get_running_batches() assert len(result) == 2 assert 'body' in result[0]['message'] assert result[1]['message']['author_id'] == 1 # batch_update() - def test_batch_update(self): + def test_conversations_batch_update(self): from pycanvas.process import Process conversation_ids= [1, 2] this_event = "mark_as_read" - result = self.canvas.batch_update(event=this_event, conversation_ids=conversation_ids) + result = self.canvas.conversations_batch_update(event=this_event, conversation_ids=conversation_ids) assert isinstance(result, Process) - def test_batch_updated_fail_on_event(self): + def test_conversations_batch_updated_fail_on_event(self): conversation_ids= [1, 2] this_event = "this doesn't work" - result = self.canvas.batch_update(event=this_event, conversation_ids=conversation_ids) + result = self.canvas.conversations_batch_update(event=this_event, conversation_ids=conversation_ids) assert isinstance(result, ValueError) - def test_batch_updated_fail_on_ids(self): + def test_conversations_batch_updated_fail_on_ids(self): conversation_ids = [None] * 501 this_event = "mark_as_read" - result = self.canvas.batch_update(event=this_event, conversation_ids=conversation_ids) + result = self.canvas.conversations_batch_update(event=this_event, conversation_ids=conversation_ids) assert isinstance(result, ValueError) From 9cf3cd412412759fafd3375d5cfc912b6ad2c822 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Fri, 29 Jul 2016 11:44:28 -0400 Subject: [PATCH 066/145] made pep8 fixes --- pycanvas/canvas.py | 21 +++++++++++---------- pycanvas/conversation.py | 10 +++++----- pycanvas/process.py | 1 + tests/test_canvas.py | 6 +++--- tests/test_conversation.py | 2 +- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index 388c44a7..cd56611d 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -6,6 +6,7 @@ from pycanvas.group import Group from pycanvas.util import combine_kwargs + class Canvas(object): """ The main class to be instantiated to provide access to Canvas's API. @@ -406,7 +407,7 @@ def conversations_mark_all_as_read(self): Mark all conversations as read. :calls: `POST /api/v1/conversations/mark_all_as_read \ `_ - + :rtype: bool """ response = self.__requester.request( @@ -421,7 +422,7 @@ def conversations_unread_count(self): :calls: `GET /api/v1/conversations/unread_count \ `_ - + :rtype: simple object with unread_count, example: {'unread_count': '7'} """ response = self.__requester.request( @@ -430,16 +431,16 @@ def conversations_unread_count(self): ) return response.json() - + def conversations_get_running_batches(self): """ Returns any currently running conversation batches for the current user. - Conversation batches are created when a bulk private message is sent + Conversation batches are created when a bulk private message is sent asynchronously. :calls: `GET /api/v1/conversations/batches \ `_ - + :rtype: dict with list of batch objects - not currently a Class """ @@ -450,12 +451,12 @@ def conversations_get_running_batches(self): return response.json() - def conversations_batch_update(self, conversation_ids, event): # IN PROGRESS + def conversations_batch_update(self, conversation_ids, event): """ - + :calls: `PUT /api/v1/conversations \ `_ - + :param conversation_ids[]: List of conversations to update. Limited to 500 conversations. :type conversation_ids: list of strings :param event: The action to take on each conversation. @@ -475,7 +476,7 @@ def conversations_batch_update(self, conversation_ids, event): # IN PROGRESS ] try: - if not event in ALLOWED_EVENTS: + if event not in ALLOWED_EVENTS: raise ValueError('%s is not a valid action. Please use one of the following: %s' % ( event, ','.join(ALLOWED_EVENTS) @@ -490,7 +491,7 @@ def conversations_batch_update(self, conversation_ids, event): # IN PROGRESS 'PUT', 'conversations', event=event, - **{"conversation_ids[]":conversation_ids} + **{"conversation_ids[]": conversation_ids} ) return_process = Process(self.__requester, response.json()) return return_process diff --git a/pycanvas/conversation.py b/pycanvas/conversation.py index e3a6d535..a7393945 100644 --- a/pycanvas/conversation.py +++ b/pycanvas/conversation.py @@ -48,7 +48,7 @@ def delete(self): else: return False - def add_recipients(self, recipients): # NEEDS TESTING ON PRODUCTION + def add_recipients(self, recipients): """ Add a recipient to a conversation. @@ -69,13 +69,13 @@ def add_recipients(self, recipients): # NEEDS TESTING ON PRODUCTION ) return Conversation(self._requester, response.json()) - def add_message(self, body, **kwargs): # NEEDS TESTING + def add_message(self, body, **kwargs): """ Add a message to a conversation. :calls: `POST /api/v1/conversations/:id/add_message \ `_ - + :param body: The body of the conversation. :type body: string :rtype: :class:`pycanvas.account.Conversation` but with only one message, the most recent one. @@ -88,7 +88,7 @@ def add_message(self, body, **kwargs): # NEEDS TESTING ) return Conversation(self._requester, response.json()) - def delete_message(self, remove): # NEEDS TESTING + def delete_message(self, remove): """ Delete messages from this conversation. Note that this only affects this user's view of the conversation. @@ -96,7 +96,7 @@ def delete_message(self, remove): # NEEDS TESTING :calls: `POST /api/v1/conversations/:id/remove_messages \ `_ - + :param remove[]: Array of message ids to be removed. :type remove: list of strings :rtype: dict diff --git a/pycanvas/process.py b/pycanvas/process.py index 9ab77c68..bb876ebf 100644 --- a/pycanvas/process.py +++ b/pycanvas/process.py @@ -1,5 +1,6 @@ from pycanvas.canvas_object import CanvasObject + class Process(CanvasObject): def __str__(self): # pragma: no cover diff --git a/tests/test_canvas.py b/tests/test_canvas.py index e8ed11ef..a7eb9c8f 100644 --- a/tests/test_canvas.py +++ b/tests/test_canvas.py @@ -207,7 +207,7 @@ def test_get_group(self): assert isinstance(group, Group) assert hasattr(group, 'name') assert hasattr(group, 'description') - + # create_conversation() def test_create_conversation(self): recipients = ['1', '2'] @@ -254,13 +254,13 @@ def test_conversations_get_running_batches(self): # batch_update() def test_conversations_batch_update(self): from pycanvas.process import Process - conversation_ids= [1, 2] + conversation_ids = [1, 2] this_event = "mark_as_read" result = self.canvas.conversations_batch_update(event=this_event, conversation_ids=conversation_ids) assert isinstance(result, Process) def test_conversations_batch_updated_fail_on_event(self): - conversation_ids= [1, 2] + conversation_ids = [1, 2] this_event = "this doesn't work" result = self.canvas.conversations_batch_update(event=this_event, conversation_ids=conversation_ids) assert isinstance(result, ValueError) diff --git a/tests/test_conversation.py b/tests/test_conversation.py index 6557a8b9..fae35796 100644 --- a/tests/test_conversation.py +++ b/tests/test_conversation.py @@ -64,7 +64,7 @@ def test_add_recipients(self): recipients = {'bob': 1, 'joe': 2} string_bob = "Bob was added to the conversation by Hank TA" string_joe = "Joe was added to the conversation by Hank TA" - result = self.conversation.add_recipients([recipients['bob'],recipients['joe']]) + result = self.conversation.add_recipients([recipients['bob'], recipients['joe']]) assert hasattr(result, 'messages') assert len(result.messages) == 2 assert result.messages[0]["body"] == string_bob From cf6e3e505f250579a5a1de73d0710b79709875cd Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Mon, 1 Aug 2016 10:16:45 -0400 Subject: [PATCH 067/145] added tests for parent types --- pycanvas/page.py | 15 +++++++++++-- tests/test_page.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/pycanvas/page.py b/pycanvas/page.py index 11468bf3..96ca6db7 100644 --- a/pycanvas/page.py +++ b/pycanvas/page.py @@ -127,7 +127,13 @@ def get_revision_by_id(self, revision_id): 'GET', '%ss/%s/pages/%s/revisions/%s' % (self.parent_type, self.parent_id, self.url, revision_id), ) - return PageRevision(self._requester, response.json()) + 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}) + + return PageRevision(self._requester, pagerev_json) def list_revisions(self, **kwargs): """ @@ -162,8 +168,13 @@ def revert_to_revision(self, revision_id): 'POST', '%ss/%s/pages/%s/revisions/%s' % (self.parent_type, self.parent_id, self.url, revision_id), ) - return PageRevision(self._requester, response.json()) + 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}) + return PageRevision(self._requester, pagerev_json) class PageRevision(CanvasObject): diff --git a/tests/test_page.py b/tests/test_page.py index f981814e..fb475df4 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -110,3 +110,57 @@ def test_get_parent_course(self): def test_get_parent_group(self): assert isinstance(self.page_group.get_parent(), Group) + + +class TestPageRevision(unittest.TestCase): + """ + Tests PageRevision methods + """ + @classmethod + def setUpClass(self): + requires = { + 'course': ['get_by_id', 'get_page'], + 'group': ['get_single_group', 'get_page'], + 'generic': ['not_found'], + 'page': ['get_latest_rev_by_id'] + } + + adapter = requests_mock.Adapter() + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) + register_uris(settings.BASE_URL, requires, adapter) + + self.course = self.canvas.get_course(1) + self.group = self.canvas.get_group(1) + self.page_course = self.course.get_page('my-url') + self.page_group = self.group.get_page('my-url') + self.revision = self.page_course.get_revision_by_id(2) + + # __str__() + def test__str__(self): + string = str(self.page_course) + assert isinstance(string, str) + + # parent_id + def test_parent_id_course(self): + assert self.revision.parent_id == 1 + + def test_parent_id_no_id(self): + page = PageRevision(self.canvas._Canvas__requester, {'url': 'my-url'}) + with self.assertRaises(ValueError): + page.parent_id + + # parent_type + def test_parent_type_course(self): + assert self.page_course.parent_type == 'course' + + def test_parent_type_group(self): + assert self.page_group.parent_type == 'group' + + def test_parent_type_no_id(self): + page = PageRevision(self.canvas._Canvas__requester, {'url': 'my-url'}) + with self.assertRaises(ValueError): + page.parent_type + + # get_parent() + def test_get_parent_course(self): + assert isinstance(self.page_course.get_parent(), Course) From 88c58b67099cc84600ef17a9a12d5b56ffd24a7e Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Mon, 1 Aug 2016 10:39:57 -0400 Subject: [PATCH 068/145] finished tests again --- pycanvas/page.py | 4 ++-- tests/fixtures/page.json | 21 +++++++++++++++++++++ tests/test_page.py | 27 +++++++++++++++++++++------ 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/pycanvas/page.py b/pycanvas/page.py index 96ca6db7..08a41fc5 100644 --- a/pycanvas/page.py +++ b/pycanvas/page.py @@ -131,7 +131,7 @@ def get_revision_by_id(self, revision_id): 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({'course_id': self.id}) return PageRevision(self._requester, pagerev_json) @@ -182,7 +182,7 @@ class PageRevision(CanvasObject): def __str__(self): return "revision_id: %s, updated_at: %s" % ( self.id, - self.update + self.updated_at ) @property diff --git a/tests/fixtures/page.json b/tests/fixtures/page.json index ae6b3ea2..78295ab2 100644 --- a/tests/fixtures/page.json +++ b/tests/fixtures/page.json @@ -75,6 +75,17 @@ "get_latest_rev_by_id": { "method": "GET", "endpoint": "courses/1/pages/my-url/revisions/2", + "data": { + "id": 1, + "updated_at": "2012-08-07T11:23:58-06:00", + "revision_id": 2, + "url": "my-url" + }, + "status_code": 200 + }, + "get_latest_rev_by_id_group": { + "method": "GET", + "endpoint": "groups/1/pages/my-url/revisions/2", "data": { "id": 1, "revision_id": 2, @@ -91,5 +102,15 @@ "url": "my-url" }, "status_code": 200 + }, + "revert_to_revision_group": { + "method": "POST", + "endpoint": "groups/1/pages/my-url/revisions/3", + "data": { + "id": 1, + "revision_id": 3, + "url": "my-url" + }, + "status_code": 200 } } \ No newline at end of file diff --git a/tests/test_page.py b/tests/test_page.py index fb475df4..fc3a6f68 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -24,7 +24,8 @@ def setUpClass(self): 'get_page', 'edit', 'delete_page', 'list_revisions', 'list_revisions2', 'latest_revision', 'get_latest_rev_by_id', - 'revert_to_revision' + 'get_latest_rev_by_id_group', 'revert_to_revision', + 'revert_to_revision_group' ] } adapter = requests_mock.Adapter() @@ -70,16 +71,26 @@ def test_show_latest_revision(self): assert isinstance(revision, PageRevision) - def test_get_revision_by_id(self): + def test_get_revision_by_id_course(self): revision = self.page_course.get_revision_by_id(2) assert isinstance(revision, PageRevision) - def test_revert_to_revision(self): + def test_get_revision_by_id_group(self): + revision = self.page_group.get_revision_by_id(2) + + assert isinstance(revision, PageRevision) + + def test_revert_to_revision_course(self): revision = self.page_course.revert_to_revision(3) assert isinstance(revision, PageRevision) + def test_revert_to_revision_group(self): + revision = self.page_group.revert_to_revision(3) + + assert isinstance(revision, PageRevision) + # parent_id def test_parent_id_course(self): assert self.page_course.parent_id == 1 @@ -122,7 +133,7 @@ def setUpClass(self): 'course': ['get_by_id', 'get_page'], 'group': ['get_single_group', 'get_page'], 'generic': ['not_found'], - 'page': ['get_latest_rev_by_id'] + 'page': ['get_latest_rev_by_id', 'get_latest_rev_by_id_group'] } adapter = requests_mock.Adapter() @@ -134,10 +145,11 @@ def setUpClass(self): self.page_course = self.course.get_page('my-url') self.page_group = self.group.get_page('my-url') self.revision = self.page_course.get_revision_by_id(2) + self.group_revision = self.page_group.get_revision_by_id(2) # __str__() def test__str__(self): - string = str(self.page_course) + string = str(self.revision) assert isinstance(string, str) # parent_id @@ -163,4 +175,7 @@ def test_parent_type_no_id(self): # get_parent() def test_get_parent_course(self): - assert isinstance(self.page_course.get_parent(), Course) + assert isinstance(self.revision.get_parent(), Course) + + def test_get_parent_group(self): + assert isinstance(self.group_revision.get_parent(), Group) From 6789fe3ffc8520a1e4c877ad637884d9f0191026 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Mon, 1 Aug 2016 10:40:56 -0400 Subject: [PATCH 069/145] cleaned up documentation --- pycanvas/canvas.py | 29 +++++++++++++++-------------- pycanvas/conversation.py | 20 ++++++++++---------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index cd56611d..d8327cc3 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -342,14 +342,15 @@ def create_conversation(self, recipients, body, **kwargs): :calls: `POST /api/v1/conversations \ `_ - :param recipients[]: An array of recipient ids. + :param recipients: An array of recipient ids. These may be user ids or course/group ids prefixed with 'course_' or 'group_' respectively, - e.g. recipients[]=1&recipients=2&recipients[]=course_3 - :type recipients[]: string array + e.g. recipients=['1', '2', 'course_3'] + :type recipients: `list` of `str` :param body: The body of the message being added. - :type body: string - :rtype: :class:`pycanvas.account.Conversation` + :type body: `str` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of \ + :class: `pycanvas.conversation.Conversation` """ from pycanvas.conversation import Conversation @@ -368,10 +369,10 @@ def get_conversation(self, conversation_id, **kwargs): Return single Conversation :calls: `GET /api/v1/conversations/:id \ - ` + `_ :param conversation_id: The ID of the conversation. - :type conversation_id: int + :type conversation_id: `int` :rtype: :class:`pycanvas.conversation.Conversation` """ from pycanvas.conversation import Conversation @@ -389,8 +390,8 @@ def get_conversations(self, **kwargs): :calls: `GET /api/v1/conversations \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of - :class:`pycanvas.conversation.Conversation` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of \ + :class:`pycanvas.conversation.Conversation` """ from pycanvas.conversation import Conversation @@ -408,7 +409,7 @@ def conversations_mark_all_as_read(self): :calls: `POST /api/v1/conversations/mark_all_as_read \ `_ - :rtype: bool + :rtype: `bool` """ response = self.__requester.request( 'POST', @@ -457,11 +458,11 @@ def conversations_batch_update(self, conversation_ids, event): :calls: `PUT /api/v1/conversations \ `_ - :param conversation_ids[]: List of conversations to update. Limited to 500 conversations. - :type conversation_ids: list of strings + :param conversation_ids: List of conversations to update. Limited to 500 conversations. + :type conversation_ids: `list` of `str` :param event: The action to take on each conversation. - :type event: string - :rtype: json object for a Progress - currently undefined class + :type event: `str` + :rtype: :class:`pycanvas.process.Process` """ from pycanvas.process import Process diff --git a/pycanvas/conversation.py b/pycanvas/conversation.py index a7393945..40105afe 100644 --- a/pycanvas/conversation.py +++ b/pycanvas/conversation.py @@ -14,7 +14,7 @@ def edit(self, **kwargs): :calls: `PUT /api/v1/conversations/:id \ `_ - :rtype: bool + :rtype: `bool` """ response = self._requester.request( 'PUT', @@ -35,7 +35,7 @@ def delete(self): :calls: `DELETE /api/v1/conversations/:id \ `_ - :rtype: bool + :rtype: `bool` """ response = self._requester.request( 'DELETE', @@ -55,11 +55,11 @@ def add_recipients(self, recipients): :calls: `POST /api/v1/conversations/:id/add_recipients \ `_ - :param recipients[]: A list of recipient ids. + :param recipients: A list of string format recipient ids. These may be user ids or course/group ids prefixed with 'course_' or 'group_' respectively, - e.g. recipients[]=1&recipients=2&recipients[]=course_3 - :type recipients[]: string list + e.g. recipients['1', '2', 'course_3'] + :type recipients: `list` of `str` :rtype: :class:`pycanvas.account.Conversation` """ response = self._requester.request( @@ -77,8 +77,8 @@ def add_message(self, body, **kwargs): `_ :param body: The body of the conversation. - :type body: string - :rtype: :class:`pycanvas.account.Conversation` but with only one message, the most recent one. + :type body: str + :rtype: :class:`pycanvas.account.Conversation` with only the most recent message. """ response = self._requester.request( 'POST', @@ -97,9 +97,9 @@ def delete_message(self, remove): :calls: `POST /api/v1/conversations/:id/remove_messages \ `_ - :param remove[]: Array of message ids to be removed. - :type remove: list of strings - :rtype: dict + :param remove: List of message ids to be removed. + :type remove: `list` of `str` + :rtype: `dict` """ response = self._requester.request( 'POST', From 53db3d64d968b6c68d48600f104ff08f76853b4f Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 2 Aug 2016 10:19:06 -0400 Subject: [PATCH 070/145] added create section method --- pycanvas/course.py | 76 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/pycanvas/course.py b/pycanvas/course.py index cabd43d0..d784f627 100644 --- a/pycanvas/course.py +++ b/pycanvas/course.py @@ -683,6 +683,80 @@ def get_page(self, url): return Page(self._requester, page_json) + @property + def parent_id(self): + """ + Return the id of the course or group that spawned this page. + + :rtype: int + """ + if hasattr(self, 'course_id'): + return self.course_id + else: + raise ValueError("Section does not have a course_id or group_id") + + @property + def parent_type(self): + """ + Return whether the page was spawned from a course or group. + + :rtype: str + """ + if hasattr(self, 'course_id'): + return 'course' + else: + raise ValueError("ExternalTool does not have a course_id or group_id") + + def get_parent(self): + """ + Return the object that spawned this page. + + :rtype: :class:`pycanvas.group.Group` or :class:`pycanvas.course.Course` + """ + from course import Course + + response = self._requester.request( + 'GET', + '%ss/%s' % (self.parent_type, self.parent_id) + ) + + if self.parent_type == 'group': + return Group(self._requester, response.json()) + elif self.parent_type == 'course': + return Course(self._requester, response.json()) + + def list_sections(self, **kwargs): + """ + Returns the list of sections for this course. + + :calls: `GET /api/v1/courses/:course_id/sections \ + `_ + + :rtype: :class: `pycanvas.course.Sections` + """ + return PaginatedList( + Section, + self._requester, + 'GET', + 'courses/%s/sections' % (self.parent_id, {'course_id': self.id}), + **combine_kwargs(**kwargs) + ) + + def create_course_section(self): + """ + Create a new section for this course. + + :calls: `POST /api/v1/courses/:course_id/sections \ + `_ + + :rtype: :class:`pycanvas.course.Section` + """ + response = self._requester.request( + 'POST', + 'courses/%s/sections' % (self.parent_id) + ) + return Section(self._requester, response.json()) + class CourseNickname(CanvasObject): @@ -710,7 +784,7 @@ def remove(self): return CourseNickname(self._requester, response.json()) -class CourseSection(CanvasObject): +class Section(CanvasObject): def __str__(self): return "id: %s, name: %s: " % ( From 3eb2f7fe9f3521dc4c82f7b00a7a4dc9c7b3db83 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 2 Aug 2016 10:19:22 -0400 Subject: [PATCH 071/145] Added tests --- tests/test_course.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/test_course.py b/tests/test_course.py index a90f0c9b..9be93408 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -9,7 +9,7 @@ from util import register_uris from pycanvas import Canvas from pycanvas.assignment import Assignment -from pycanvas.course import Course, CourseNickname, Page +from pycanvas.course import Course, CourseNickname, Page, Section from pycanvas.enrollment import Enrollment from pycanvas.external_tool import ExternalTool from pycanvas.exceptions import ResourceDoesNotExist, RequiredFieldMissing @@ -34,7 +34,7 @@ def setUpClass(self): 'get_recent_students_p2', 'get_section', 'get_user', 'get_user_id_type', 'get_users', 'get_users_p2', 'list_enrollments', 'list_enrollments_2', 'list_modules', - 'list_modules2', 'list_quizzes', 'list_quizzes2', + 'list_modules2', 'list_sections', 'list_sections2', 'list_quizzes', 'list_quizzes2', 'preview_html', 'reactivate_enrollment', 'reset', 'settings', 'show_front_page', 'update', 'update_settings', 'upload', 'upload_final' @@ -374,6 +374,18 @@ def test_get_external_tools(self): assert isinstance(tool_list[0], ExternalTool) assert len(tool_list) == 4 + def test_list_sections(self): + sections = self.course.list_sections() + section_list = [sect for sect in sections] + + assert isinstance(section_list[0], Section) + assert len(section_list, Section) + + def test_create_course_section(self): + section = self.course.create_course_section() + + assert isinstance(section, Section) + class TestCourseNickname(unittest.TestCase): """ From cecdecf34856c2e9caa7ab42a7175909005edcd6 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 2 Aug 2016 10:24:27 -0400 Subject: [PATCH 072/145] added fixtures for list sections --- tests/fixtures/course.json | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/fixtures/course.json b/tests/fixtures/course.json index 5767d5d3..55d72fcd 100644 --- a/tests/fixtures/course.json +++ b/tests/fixtures/course.json @@ -675,5 +675,38 @@ "url": "my-url" }, "status_code": 200 + }, + "list_sections": { + "method": "GET", + "endpoint": "courses/1/sections", + "data": [ + { + "id": 1, + "name": "Section 1" + }, + { + "id": 2, + "name": "Section 2" + } + ], + "status_code": 200, + "headers": { + "Link": "; rel=\"next\"" + } + }, + "list_sections2": { + "method": "GET", + "endpoint": "courses/1/list_sections?page=2&per_page=2", + "data": [ + { + "id": 3, + "name": "Section 3" + }, + { + "id": 4, + "name": "Section 4" + } + ], + "status_code": 200 } } \ No newline at end of file From e76166aeaab8b8fb93fe7c31792c333244bc4980 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 2 Aug 2016 11:01:59 -0400 Subject: [PATCH 073/145] added several methods --- pycanvas/section.py | 62 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/pycanvas/section.py b/pycanvas/section.py index c1c5fc46..7e33f602 100644 --- a/pycanvas/section.py +++ b/pycanvas/section.py @@ -30,3 +30,65 @@ def get_enrollments(self, **kwargs): 'sections/%s/enrollments' % (self.id), **combine_kwargs(**kwargs) ) + + #Need help + def cross_list_section(self): + """ + Move the Section to another course. + + :calls: `POST /api/v1/sections/:id/crosslist/:new_course_id + \ + `_ + + :rtype: :class:`pycanvas.section.Section` + """ + from course import Course + response = self._requester.request( + 'POST', + 'sections/%s/crosslist/%s' + ) + #Need help + def decross_list_section(self): + """ + Undo cross-listing of a section. + + :calls: `DELETE /api/v1/sections/:id/crosslist \ + `_ + + :rtype: :class:`pycanvas.section.Section` + """ + response = self._requester.request( + "DELETE", + "sections/%s/crosslist" % (self.id) + ) + return Section(self._requester, response.json()) + + def edit(self): + """ + Edit contents of a target section. + + :calls: `PUT /api/v1/sections/:id \ + `_ + + :rtype: :class:`pycanvas.section.Section` + """ + response = self._requester.request( + "PUT", + "sections/%s" % (self.id) + ) + return Section(self._requester, response.json()) + + def delete(self): + """ + Delete a target section. + + :calls: `DELETE /api/v1/sections/:id \ + `_ + + :rtype: :class:`pycanvas.section.Section` + """ + response = self._requester.request( + "DELETE", + "sections/%s" % (self.id) + ) + return Section(self._requester, response.json()) From 1814b5b45d9bbf5fb38ff3d7362713fa6b14e30b Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 2 Aug 2016 11:02:26 -0400 Subject: [PATCH 074/145] Added tests for decross list section --- tests/test_section.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_section.py b/tests/test_section.py index 7d2ec8fd..cc208e2f 100644 --- a/tests/test_section.py +++ b/tests/test_section.py @@ -5,6 +5,7 @@ from util import register_uris from pycanvas.enrollment import Enrollment from pycanvas import Canvas +from pycanvas import Section class TestSection(unittest.TestCase): @@ -15,7 +16,7 @@ class TestSection(unittest.TestCase): def setUpClass(self): requires = { 'generic': ['not_found'], - 'section': ['get_by_id', 'list_enrollments', 'list_enrollments_2'] + 'section': ['decross_section', 'get_by_id', 'list_enrollments', 'list_enrollments_2'] } adapter = requests_mock.Adapter() @@ -36,3 +37,8 @@ def test_get_enrollments(self): assert len(enrollment_list) == 4 assert isinstance(enrollment_list[0], Enrollment) + + def test_decross_list_section(self): + section = self.section.test_decross_list_section() + + assert isinstance(section, Section) From a513bba7189d977c1b6be55d81e8e931630cb9e6 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 2 Aug 2016 11:02:46 -0400 Subject: [PATCH 075/145] Added fixture for decross list section --- tests/fixtures/section.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/fixtures/section.json b/tests/fixtures/section.json index 141db2a2..6a7a7f53 100644 --- a/tests/fixtures/section.json +++ b/tests/fixtures/section.json @@ -45,5 +45,14 @@ } ], "status_code": 200 + }, + "decross_section": { + "method": "DELETE", + "endpoint": "sections/1/crosslist", + "data": { + "id": 1, + "name": "Target Section" + }, + "status_code": 200 } } \ No newline at end of file From b4ba07ea45de14cf93364cf148e63e6c2770408a Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 2 Aug 2016 11:03:15 -0400 Subject: [PATCH 076/145] added tests and correct fixture --- tests/test_course.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_course.py b/tests/test_course.py index 9be93408..bd425fc8 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -25,7 +25,7 @@ class TestCourse(unittest.TestCase): def setUpClass(self): requires = { 'course': [ - 'create', 'create_assignment', 'create_module', 'create_page', + 'create', 'create_assignment', 'create_section', 'create_module', 'create_page', 'deactivate_enrollment', 'edit_front_page', 'enroll_user', 'get_all_assignments', 'get_all_assignments2', 'get_assignment_by_id', 'get_by_id', 'get_external_tools', From 728fa570d4bd45ce96b8a9e002bc96d3adb6ed69 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 2 Aug 2016 11:03:36 -0400 Subject: [PATCH 077/145] added a fixture for create course section --- tests/fixtures/course.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/fixtures/course.json b/tests/fixtures/course.json index 55d72fcd..6efa7db7 100644 --- a/tests/fixtures/course.json +++ b/tests/fixtures/course.json @@ -708,5 +708,14 @@ } ], "status_code": 200 + }, + "create_section": { + "method": "POST", + "endpoint": "courses/1/sections", + "data": { + "id": 1, + "name": "New Section" + }, + "status_code": 200 } } \ No newline at end of file From 56af686318730f8fa10c69cde1a7a1e59dec7256 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 2 Aug 2016 11:10:16 -0400 Subject: [PATCH 078/145] added two fixtures for edit and delete --- tests/fixtures/section.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/fixtures/section.json b/tests/fixtures/section.json index 6a7a7f53..5cf960c2 100644 --- a/tests/fixtures/section.json +++ b/tests/fixtures/section.json @@ -54,5 +54,23 @@ "name": "Target Section" }, "status_code": 200 + }, + "edit": { + "method": "PUT", + "endpoint": "sections/1", + "data": { + "id": 1, + "name": "Target Edit" + }, + "status_code": 200 + }, + "delete": { + "method": "DELETE", + "endpoint": "sections/1", + "data": { + "id": 1, + "name": "Deleted Section" + }, + "status_code": 200 } } \ No newline at end of file From ac129029797bb57d3e994c5deb85bdbe5f1dd306 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 2 Aug 2016 11:52:17 -0400 Subject: [PATCH 079/145] added correct import statement --- tests/test_section.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/tests/test_section.py b/tests/test_section.py index cc208e2f..48606975 100644 --- a/tests/test_section.py +++ b/tests/test_section.py @@ -5,7 +5,7 @@ from util import register_uris from pycanvas.enrollment import Enrollment from pycanvas import Canvas -from pycanvas import Section +from pycanvas.section import Section class TestSection(unittest.TestCase): @@ -16,7 +16,11 @@ class TestSection(unittest.TestCase): def setUpClass(self): requires = { 'generic': ['not_found'], - 'section': ['decross_section', 'get_by_id', 'list_enrollments', 'list_enrollments_2'] + 'section': [ + 'decross_section', 'delete', 'edit', + 'get_by_id', 'list_enrollments', + 'list_enrollments_2' + ] } adapter = requests_mock.Adapter() @@ -39,6 +43,16 @@ def test_get_enrollments(self): assert isinstance(enrollment_list[0], Enrollment) def test_decross_list_section(self): - section = self.section.test_decross_list_section() + section = self.section.decross_list_section() assert isinstance(section, Section) + + def test_edit(self): + edit = self.section.edit() + + assert isinstance(edit, Section) + + def test_delete(self): + deleted_section = self.section.delete() + + assert isinstance(deleted_section, Section) From 077fcb216c313f477eff13ac8ad658cec6432e1f Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 2 Aug 2016 12:08:38 -0400 Subject: [PATCH 080/145] fixed cross list section test --- tests/test_section.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_section.py b/tests/test_section.py index 48606975..30742045 100644 --- a/tests/test_section.py +++ b/tests/test_section.py @@ -17,8 +17,8 @@ def setUpClass(self): requires = { 'generic': ['not_found'], 'section': [ - 'decross_section', 'delete', 'edit', - 'get_by_id', 'list_enrollments', + 'crosslist_section', 'decross_section', 'delete', + 'edit', 'get_by_id', 'list_enrollments', 'list_enrollments_2' ] } @@ -42,6 +42,11 @@ def test_get_enrollments(self): assert len(enrollment_list) == 4 assert isinstance(enrollment_list[0], Enrollment) + def test_cross_list_section(self): + section = self.section.cross_list_section(2) + + assert isinstance(section, Section) + def test_decross_list_section(self): section = self.section.decross_list_section() From dde6c1b99771a398dfa817037f9b937b6d831c36 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 2 Aug 2016 12:08:56 -0400 Subject: [PATCH 081/145] Added the rest of cross list section method --- pycanvas/section.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pycanvas/section.py b/pycanvas/section.py index 7e33f602..a686d74f 100644 --- a/pycanvas/section.py +++ b/pycanvas/section.py @@ -32,7 +32,7 @@ def get_enrollments(self, **kwargs): ) #Need help - def cross_list_section(self): + def cross_list_section(self, new_course_id): """ Move the Section to another course. @@ -42,12 +42,12 @@ def cross_list_section(self): :rtype: :class:`pycanvas.section.Section` """ - from course import Course response = self._requester.request( 'POST', - 'sections/%s/crosslist/%s' + 'sections/%s/crosslist/%s' % (self.id, new_course_id) ) - #Need help + return Section(self._requester, response.json()) + def decross_list_section(self): """ Undo cross-listing of a section. From 6282043deaff64e6c1549ba8ccb3fd3687097806 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 2 Aug 2016 12:09:19 -0400 Subject: [PATCH 082/145] added fixture for crosslist section --- tests/fixtures/section.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/fixtures/section.json b/tests/fixtures/section.json index 5cf960c2..291201f0 100644 --- a/tests/fixtures/section.json +++ b/tests/fixtures/section.json @@ -46,6 +46,16 @@ ], "status_code": 200 }, + "crosslist_section":{ + "method": "POST", + "endpoint": "sections/1/crosslist/2", + "data": { + "id": 1, + "new_course_id": 2, + "name": "Cross Section" + }, + "status_code": 200 + }, "decross_section": { "method": "DELETE", "endpoint": "sections/1/crosslist", From e5e98e94ff320e5926405989074676aaf7632a21 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 2 Aug 2016 12:36:44 -0400 Subject: [PATCH 083/145] capturing the courseids --- pycanvas/course.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pycanvas/course.py b/pycanvas/course.py index d784f627..c7b12371 100644 --- a/pycanvas/course.py +++ b/pycanvas/course.py @@ -734,11 +734,12 @@ def list_sections(self, **kwargs): :rtype: :class: `pycanvas.course.Sections` """ + from section import Section return PaginatedList( Section, self._requester, 'GET', - 'courses/%s/sections' % (self.parent_id, {'course_id': self.id}), + 'courses/%s/sections' % (self.parent_id), **combine_kwargs(**kwargs) ) @@ -751,11 +752,15 @@ def create_course_section(self): :rtype: :class:`pycanvas.course.Section` """ + from section import Section response = self._requester.request( 'POST', 'courses/%s/sections' % (self.parent_id) ) - return Section(self._requester, response.json()) + section_json = response.json() + section_json.update({'course_id': self.id}) + + return Section(self._requester, section_json) class CourseNickname(CanvasObject): From cce7218d4f831cd8803f22bb627c4d3292a0285a Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Tue, 2 Aug 2016 13:27:10 -0400 Subject: [PATCH 084/145] finished sections --- pycanvas/course.py | 63 +++++--------------------------------------- tests/test_course.py | 4 +-- 2 files changed, 8 insertions(+), 59 deletions(-) diff --git a/pycanvas/course.py b/pycanvas/course.py index c7b12371..f01f495a 100644 --- a/pycanvas/course.py +++ b/pycanvas/course.py @@ -683,48 +683,6 @@ def get_page(self, url): return Page(self._requester, page_json) - @property - def parent_id(self): - """ - Return the id of the course or group that spawned this page. - - :rtype: int - """ - if hasattr(self, 'course_id'): - return self.course_id - else: - raise ValueError("Section does not have a course_id or group_id") - - @property - def parent_type(self): - """ - Return whether the page was spawned from a course or group. - - :rtype: str - """ - if hasattr(self, 'course_id'): - return 'course' - else: - raise ValueError("ExternalTool does not have a course_id or group_id") - - def get_parent(self): - """ - Return the object that spawned this page. - - :rtype: :class:`pycanvas.group.Group` or :class:`pycanvas.course.Course` - """ - from course import Course - - response = self._requester.request( - 'GET', - '%ss/%s' % (self.parent_type, self.parent_id) - ) - - if self.parent_type == 'group': - return Group(self._requester, response.json()) - elif self.parent_type == 'course': - return Course(self._requester, response.json()) - def list_sections(self, **kwargs): """ Returns the list of sections for this course. @@ -739,11 +697,11 @@ def list_sections(self, **kwargs): Section, self._requester, 'GET', - 'courses/%s/sections' % (self.parent_id), + 'courses/%s/sections' % (self.id), **combine_kwargs(**kwargs) ) - def create_course_section(self): + def create_course_section(self, **kwargs): """ Create a new section for this course. @@ -752,15 +710,15 @@ def create_course_section(self): :rtype: :class:`pycanvas.course.Section` """ + from section import Section response = self._requester.request( 'POST', - 'courses/%s/sections' % (self.parent_id) + 'courses/%s/sections' % (self.id), + **combine_kwargs(**kwargs) ) - section_json = response.json() - section_json.update({'course_id': self.id}) - return Section(self._requester, section_json) + return Section(self._requester, response.json()) class CourseNickname(CanvasObject): @@ -787,12 +745,3 @@ def remove(self): 'users/self/course_nicknames/%s' % (self.course_id) ) return CourseNickname(self._requester, response.json()) - - -class Section(CanvasObject): - - def __str__(self): - return "id: %s, name: %s: " % ( - self.id, - self.name - ) diff --git a/tests/test_course.py b/tests/test_course.py index bd425fc8..9a3d86df 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -9,7 +9,7 @@ from util import register_uris from pycanvas import Canvas from pycanvas.assignment import Assignment -from pycanvas.course import Course, CourseNickname, Page, Section +from pycanvas.course import Course, CourseNickname, Page from pycanvas.enrollment import Enrollment from pycanvas.external_tool import ExternalTool from pycanvas.exceptions import ResourceDoesNotExist, RequiredFieldMissing @@ -379,7 +379,7 @@ def test_list_sections(self): section_list = [sect for sect in sections] assert isinstance(section_list[0], Section) - assert len(section_list, Section) + assert len(section_list) == 4 def test_create_course_section(self): section = self.course.create_course_section() From dcb5e3026148841fa2503f864dc220f1e6aef4e9 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Wed, 3 Aug 2016 09:55:20 -0400 Subject: [PATCH 085/145] added conversation to sphinx docs. small formatting fixes --- docs/class-reference.rst | 2 ++ docs/conversation-ref.rst | 6 ++++++ docs/process-ref.rst | 6 ++++++ pycanvas/canvas.py | 26 +++++++++++++++----------- 4 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 docs/conversation-ref.rst create mode 100644 docs/process-ref.rst diff --git a/docs/class-reference.rst b/docs/class-reference.rst index 890472e7..adaf3639 100644 --- a/docs/class-reference.rst +++ b/docs/class-reference.rst @@ -6,10 +6,12 @@ Class Reference canvas-ref account-ref assignment-ref + conversation-ref course-ref external-tool-ref module-ref page-ref + process-ref quiz-ref section-ref user-ref diff --git a/docs/conversation-ref.rst b/docs/conversation-ref.rst new file mode 100644 index 00000000..560ef4ec --- /dev/null +++ b/docs/conversation-ref.rst @@ -0,0 +1,6 @@ +============ +Conversation +============ + +.. autoclass:: pycanvas.conversation.Conversation + :members: diff --git a/docs/process-ref.rst b/docs/process-ref.rst new file mode 100644 index 00000000..9ffd1e6f --- /dev/null +++ b/docs/process-ref.rst @@ -0,0 +1,6 @@ +============ +Process +============ + +.. autoclass:: pycanvas.process.Process + :members: diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index d8327cc3..a254305d 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -214,8 +214,8 @@ def get_course_nicknames(self): :calls: `GET /api/v1/users/self/course_nicknames \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of \ - :class:`pycanvas.course_nickname.CourseNickname` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.course_nickname.CourseNickname` """ from pycanvas.course import CourseNickname @@ -349,8 +349,8 @@ def create_conversation(self, recipients, body, **kwargs): :type recipients: `list` of `str` :param body: The body of the message being added. :type body: `str` - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of \ - :class: `pycanvas.conversation.Conversation` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.conversation.Conversation` """ from pycanvas.conversation import Conversation @@ -478,15 +478,19 @@ def conversations_batch_update(self, conversation_ids, event): try: if event not in ALLOWED_EVENTS: - raise ValueError('%s is not a valid action. Please use one of the following: %s' % ( - event, - ','.join(ALLOWED_EVENTS) - )) + raise ValueError( + '%s is not a valid action. Please use one of the following: %s' % ( + event, + ','.join(ALLOWED_EVENTS) + ) + ) if len(conversation_ids) > 500: - raise ValueError('You have requested %s updates, which exceeds the limit of 500' % ( - len(conversation_ids) - )) + raise ValueError( + 'You have requested %s updates, which exceeds the limit of 500' % ( + len(conversation_ids) + ) + ) response = self.__requester.request( 'PUT', From b648bf6423031a696868f1f0b9da557acdeb7d6e Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Wed, 3 Aug 2016 10:33:29 -0400 Subject: [PATCH 086/145] added some documentation stuff --- docs/class-reference.rst | 1 + docs/group-ref.rst | 6 ++++++ docs/page-ref.rst | 7 +++++++ pycanvas/page.py | 4 ++-- 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 docs/group-ref.rst diff --git a/docs/class-reference.rst b/docs/class-reference.rst index 890472e7..1c7f35d7 100644 --- a/docs/class-reference.rst +++ b/docs/class-reference.rst @@ -8,6 +8,7 @@ Class Reference assignment-ref course-ref external-tool-ref + group-ref module-ref page-ref quiz-ref diff --git a/docs/group-ref.rst b/docs/group-ref.rst new file mode 100644 index 00000000..66d1a610 --- /dev/null +++ b/docs/group-ref.rst @@ -0,0 +1,6 @@ +===== +Group +===== + +.. autoclass:: pycanvas.group.Group + :members: diff --git a/docs/page-ref.rst b/docs/page-ref.rst index 1ca6d23b..bb132b62 100644 --- a/docs/page-ref.rst +++ b/docs/page-ref.rst @@ -4,3 +4,10 @@ Page .. autoclass:: pycanvas.page.Page :members: + + +============= +Page Revision +============= +.. autoclass:: pycanvas.page.PageRevision + :members: diff --git a/pycanvas/page.py b/pycanvas/page.py index 08a41fc5..12914fbe 100644 --- a/pycanvas/page.py +++ b/pycanvas/page.py @@ -20,7 +20,7 @@ def edit(self, **kwargs): :calls: `PUT /api/v1/courses/:course_id/pages/:url \ `_ - :rtype: :class: `pycanvas.course.Course` + :rtype: :class:`pycanvas.course.Course` """ response = self._requester.request( 'PUT', @@ -41,7 +41,7 @@ def delete(self): :calls: `DELETE /api/v1/courses/:course_id/pages/:url \ `_ - :rtype: :class: `pycanvas.course.Course` + :rtype: :class:`pycanvas.course.Course` """ response = self._requester.request( 'DELETE', From 8aba5dd865e51d9fff8bdcc7bb2ade47aadea4b8 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Wed, 3 Aug 2016 10:40:04 -0400 Subject: [PATCH 087/145] addressed merge comments --- pycanvas/canvas.py | 6 ++++-- pycanvas/conversation.py | 2 +- pycanvas/requester.py | 9 --------- tests/test_conversation.py | 16 +++++----------- 4 files changed, 10 insertions(+), 23 deletions(-) diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index d8327cc3..babb91ff 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -424,7 +424,8 @@ def conversations_unread_count(self): :calls: `GET /api/v1/conversations/unread_count \ `_ - :rtype: simple object with unread_count, example: {'unread_count': '7'} + :returns: simple object with unread_count, example: {'unread_count': '7'} + :rtype: `dict` """ response = self.__requester.request( 'GET', @@ -442,7 +443,8 @@ def conversations_get_running_batches(self): :calls: `GET /api/v1/conversations/batches \ `_ - :rtype: dict with list of batch objects - not currently a Class + :returns: dict with list of batch objects - not currently a Class + :rtype: `dict` """ response = self.__requester.request( diff --git a/pycanvas/conversation.py b/pycanvas/conversation.py index 40105afe..07c6453a 100644 --- a/pycanvas/conversation.py +++ b/pycanvas/conversation.py @@ -88,7 +88,7 @@ def add_message(self, body, **kwargs): ) return Conversation(self._requester, response.json()) - def delete_message(self, remove): + def delete_messages(self, remove): """ Delete messages from this conversation. Note that this only affects this user's view of the conversation. diff --git a/pycanvas/requester.py b/pycanvas/requester.py index 90cb0629..944d8225 100644 --- a/pycanvas/requester.py +++ b/pycanvas/requester.py @@ -119,13 +119,4 @@ def _put_request(self, url, headers, data=None): :param params: dict :param data: dict """ - # import logging - # from httplib import HTTPConnection - # HTTPConnection.debuglevel = 1 - # logging.basicConfig() - # logging.getLogger().setLevel(logging.DEBUG) - # requests_log = logging.getLogger("requests.packages.urllib3") - # requests_log.setLevel(logging.DEBUG) - # requests_log.propogate = True - return self._session.put(url, headers=headers, data=data) diff --git a/tests/test_conversation.py b/tests/test_conversation.py index fae35796..c80cee10 100644 --- a/tests/test_conversation.py +++ b/tests/test_conversation.py @@ -10,21 +10,15 @@ class TestConversation(unittest.TestCase): """ - Tests PageView functionality. + Tests Conversation functionality. """ @classmethod def setUpClass(self): requires = { 'conversation': [ - 'get_by_id', - "get_by_id_2", - 'edit_conversation', - 'edit_conversation_fail', - 'delete_conversation', - 'delete_conversation_fail', - 'add_recipients', - 'add_message', - 'delete_message' + 'add_message', 'add_recipients', 'delete_conversation', + 'delete_conversation_fail', 'delete_message', 'edit_conversation', + 'edit_conversation_fail', 'get_by_id', 'get_by_id_2' ] } @@ -81,6 +75,6 @@ def test_add_message(self): # delete_message() def test_delete_message(self): id_list = [1] - result = self.conversation.delete_message(id_list) + result = self.conversation.delete_messages(id_list) assert 'subject' in result assert result['id'] == 1 From 6b49de87c7f93fee1aa621b3ac3e01b594d4a275 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 3 Aug 2016 10:46:40 -0400 Subject: [PATCH 088/145] Made merge request changes --- pycanvas/page.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pycanvas/page.py b/pycanvas/page.py index 08a41fc5..80eafac5 100644 --- a/pycanvas/page.py +++ b/pycanvas/page.py @@ -96,7 +96,7 @@ def get_parent(self): elif self.parent_type == 'course': return Course(self._requester, response.json()) - def show_latest_revision(self): + def show_latest_revision(self, **kwargs): """ Retrieve the contents of the latest revision. @@ -108,15 +108,16 @@ def show_latest_revision(self): response = self._requester.request( 'GET', '%ss/%s/pages/%s/revisions/latest' % (self.parent_type, self.parent_id, self.url), + **combine_kwargs(**kwargs) ) return PageRevision(self._requester, response.json()) - def get_revision_by_id(self, revision_id): + def get_revision_by_id(self, revision_id, **kwargs): """ Retrieve the contents of the revision by the id. :calls: `GET /api/v1/courses/:course_id/pages/:url/revisions/:revision_id \ - + `_ :param revision_id: The id of a specified revision. :type revision_id: int @@ -126,6 +127,7 @@ def get_revision_by_id(self, revision_id): response = self._requester.request( 'GET', '%ss/%s/pages/%s/revisions/%s' % (self.parent_type, self.parent_id, self.url, revision_id), + **combine_kwargs(**kwargs) ) pagerev_json = response.json() if self.parent_type == "group": @@ -142,7 +144,7 @@ def list_revisions(self, **kwargs): :calls: `GET /api/v1/courses/:course_id/pages/:url/revisions \ `_ - :rtype: :class:`pycanvas.pagerevision.PageRevision` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of `pycanvas.pagerevision.PageRevision` """ return PaginatedList( PageRevision, @@ -157,7 +159,7 @@ def revert_to_revision(self, revision_id): Revert the page back to a specified revision. :calls: `POST /api/v1/courses/:course_id/pages/:url/revisions/:revision_id \ - `_ + `_ :param revision_id: The id of a specified revision. :type revision_id: int From 88134bfcce7ccab066d96efc9e9feb5a09f9db9d Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 3 Aug 2016 10:54:37 -0400 Subject: [PATCH 089/145] merge request changes --- pycanvas/course.py | 2 +- pycanvas/section.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pycanvas/course.py b/pycanvas/course.py index f01f495a..c05779fd 100644 --- a/pycanvas/course.py +++ b/pycanvas/course.py @@ -690,7 +690,7 @@ def list_sections(self, **kwargs): :calls: `GET /api/v1/courses/:course_id/sections \ `_ - :rtype: :class: `pycanvas.course.Sections` + :rtype: :class: `pycanvas.section.Section` """ from section import Section return PaginatedList( diff --git a/pycanvas/section.py b/pycanvas/section.py index a686d74f..2cc25720 100644 --- a/pycanvas/section.py +++ b/pycanvas/section.py @@ -31,7 +31,6 @@ def get_enrollments(self, **kwargs): **combine_kwargs(**kwargs) ) - #Need help def cross_list_section(self, new_course_id): """ Move the Section to another course. From 1d667283b9abefaddc218bd91fcc387dc635c6f4 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Wed, 3 Aug 2016 10:59:19 -0400 Subject: [PATCH 090/145] added fixes to qa --- docs/class-reference.rst | 2 ++ docs/conversation-ref.rst | 6 ++++++ docs/process-ref.rst | 6 ++++++ pycanvas/canvas.py | 26 +++++++++++++++----------- pycanvas/conversation.py | 3 ++- 5 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 docs/conversation-ref.rst create mode 100644 docs/process-ref.rst diff --git a/docs/class-reference.rst b/docs/class-reference.rst index 890472e7..adaf3639 100644 --- a/docs/class-reference.rst +++ b/docs/class-reference.rst @@ -6,10 +6,12 @@ Class Reference canvas-ref account-ref assignment-ref + conversation-ref course-ref external-tool-ref module-ref page-ref + process-ref quiz-ref section-ref user-ref diff --git a/docs/conversation-ref.rst b/docs/conversation-ref.rst new file mode 100644 index 00000000..560ef4ec --- /dev/null +++ b/docs/conversation-ref.rst @@ -0,0 +1,6 @@ +============ +Conversation +============ + +.. autoclass:: pycanvas.conversation.Conversation + :members: diff --git a/docs/process-ref.rst b/docs/process-ref.rst new file mode 100644 index 00000000..9ffd1e6f --- /dev/null +++ b/docs/process-ref.rst @@ -0,0 +1,6 @@ +============ +Process +============ + +.. autoclass:: pycanvas.process.Process + :members: diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index babb91ff..6691457c 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -214,8 +214,8 @@ def get_course_nicknames(self): :calls: `GET /api/v1/users/self/course_nicknames \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of \ - :class:`pycanvas.course_nickname.CourseNickname` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.course_nickname.CourseNickname` """ from pycanvas.course import CourseNickname @@ -349,8 +349,8 @@ def create_conversation(self, recipients, body, **kwargs): :type recipients: `list` of `str` :param body: The body of the message being added. :type body: `str` - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of \ - :class: `pycanvas.conversation.Conversation` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.conversation.Conversation` """ from pycanvas.conversation import Conversation @@ -480,15 +480,19 @@ def conversations_batch_update(self, conversation_ids, event): try: if event not in ALLOWED_EVENTS: - raise ValueError('%s is not a valid action. Please use one of the following: %s' % ( - event, - ','.join(ALLOWED_EVENTS) - )) + raise ValueError( + '%s is not a valid action. Please use one of the following: %s' % ( + event, + ','.join(ALLOWED_EVENTS) + ) + ) if len(conversation_ids) > 500: - raise ValueError('You have requested %s updates, which exceeds the limit of 500' % ( - len(conversation_ids) - )) + raise ValueError( + 'You have requested %s updates, which exceeds the limit of 500' % ( + len(conversation_ids) + ) + ) response = self.__requester.request( 'PUT', diff --git a/pycanvas/conversation.py b/pycanvas/conversation.py index 07c6453a..46bf4c14 100644 --- a/pycanvas/conversation.py +++ b/pycanvas/conversation.py @@ -78,7 +78,8 @@ def add_message(self, body, **kwargs): :param body: The body of the conversation. :type body: str - :rtype: :class:`pycanvas.account.Conversation` with only the most recent message. + :returns: `pycanvas.account.Conversation` with only the most recent message. + :rtype: :class:`pycanvas.account.Conversation` """ response = self._requester.request( 'POST', From b24bc8f24bb8b29e5103e61acdfd8fe8f5f8aec4 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Wed, 3 Aug 2016 11:00:40 -0400 Subject: [PATCH 091/145] small mr fix --- pycanvas/page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycanvas/page.py b/pycanvas/page.py index a009c1e8..42fbd616 100644 --- a/pycanvas/page.py +++ b/pycanvas/page.py @@ -144,7 +144,7 @@ def list_revisions(self, **kwargs): :calls: `GET /api/v1/courses/:course_id/pages/:url/revisions \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of `pycanvas.pagerevision.PageRevision` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.pagerevision.PageRevision` """ return PaginatedList( PageRevision, From 6d5b816ab3e805d55ec2c7585f3ccf68601b686b Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Wed, 3 Aug 2016 11:02:16 -0400 Subject: [PATCH 092/145] fixed docstring spacing --- pycanvas/page.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycanvas/page.py b/pycanvas/page.py index 42fbd616..3dc5e77a 100644 --- a/pycanvas/page.py +++ b/pycanvas/page.py @@ -122,7 +122,7 @@ def get_revision_by_id(self, revision_id, **kwargs): :param revision_id: The id of a specified revision. :type revision_id: int :returns: Contents of the page revision. - :rtype: :class: `pycanvas.pagerevision.PageRevision` + :rtype: :class:`pycanvas.pagerevision.PageRevision` """ response = self._requester.request( 'GET', @@ -164,7 +164,7 @@ def revert_to_revision(self, revision_id): :param revision_id: The id of a specified revision. :type revision_id: int :returns: Contents of the page revision. - :rtype: :class: `pycanvas.pagerevision.PageRevision` + :rtype: :class:`pycanvas.pagerevision.PageRevision` """ response = self._requester.request( 'POST', From a3ab6f42e5dea8eba90db43804972fc80e8a4129 Mon Sep 17 00:00:00 2001 From: Elise Heron Date: Fri, 5 Aug 2016 16:19:26 -0400 Subject: [PATCH 093/145] added some tests for groups --- pycanvas/account.py | 18 ++++++++++++++ pycanvas/course.py | 19 ++++++++++++++ pycanvas/user.py | 19 ++++++++++++++ tests/fixtures/account.json | 33 +++++++++++++++++++++++++ tests/fixtures/course.json | 33 +++++++++++++++++++++++++ tests/fixtures/user.json | 49 +++++++++++++++++++++++++++++++------ tests/test_account.py | 16 +++++++++--- tests/test_course.py | 13 ++++++++-- tests/test_user.py | 10 +++++++- 9 files changed, 195 insertions(+), 15 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index f77414ea..e64a90d5 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -366,6 +366,24 @@ def enroll_by_id(self, enrollment_id, **kwargs): ) return Enrollment(self._requester, response.json()) + def list_groups_in_context(self, **kwargs): + """ + Return list of active groups for the specified account. + + :calls:`GET /api/v1/accounts/:account_id/groups \ + `_ + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.Group` + """ + from group import Group + return PaginatedList( + Group, + self._requester, + 'GET', + 'accounts/%s/groups' % (self.id), + **combine_kwargs(**kwargs) + ) + class AccountNotification(CanvasObject): def __str__(self): # pragma: no cover diff --git a/pycanvas/course.py b/pycanvas/course.py index c05779fd..6b2604a1 100644 --- a/pycanvas/course.py +++ b/pycanvas/course.py @@ -720,6 +720,25 @@ def create_course_section(self, **kwargs): return Section(self._requester, response.json()) + def list_groups_in_context(self, **kwargs): + """ + Return list of active groups for the specified course. + + :calls:`GET /api/v1/courses/:course_id/groups \ + `_ + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.course.Course` + """ + from group import Group + return PaginatedList( + Group, + self._requester, + 'GET', + 'courses/%s/groups' % (self.id), + **combine_kwargs(**kwargs) + ) + + class CourseNickname(CanvasObject): diff --git a/pycanvas/user.py b/pycanvas/user.py index d46c3027..af949da9 100644 --- a/pycanvas/user.py +++ b/pycanvas/user.py @@ -275,3 +275,22 @@ def upload(self, file, **kwargs): file, **kwargs ).start() + + def list_groups(self, **kwargs): + """ + Return the list of active groups for the user. + + :calls:`GET /api/v1/users/self/groups \ + `_ + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.Group` + """ + from group import Group + + return PaginatedList( + Group, + self._requester, + 'GET', + 'users/self/groups', + **combine_kwargs(**kwargs) + ) diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json index 02ce96f3..5240887b 100644 --- a/tests/fixtures/account.json +++ b/tests/fixtures/account.json @@ -274,6 +274,39 @@ ], "status_code": 200 }, + "list_groups_context": { + "method": "GET", + "endpoint": "accounts/1/groups", + "data": [ + { + "id": 1, + "name": "Group 1" + }, + { + "id": 2, + "name": "Group 2" + } + ], + "status_code": 200, + "headers": { + "Link": "; rel=\"next\"" + } + }, + "list_groups_context2": { + "method": "GET", + "endpoint": "accounts/1/groups?page=2&per_page=2", + "data": [ + { + "id": 3, + "name": "Group 3" + }, + { + "id": 4, + "name": "Group 4" + } + ], + "status_code": 200 + }, "multiple": { "method": "GET", "endpoint": "accounts", diff --git a/tests/fixtures/course.json b/tests/fixtures/course.json index 6efa7db7..a40abe51 100644 --- a/tests/fixtures/course.json +++ b/tests/fixtures/course.json @@ -717,5 +717,38 @@ "name": "New Section" }, "status_code": 200 + }, + "list_groups_context": { + "method": "GET", + "endpoint": "courses/1/groups", + "data": [ + { + "id": 1, + "name": "Group 1" + }, + { + "id": 2, + "name": "Group 2" + } + ], + "status_code": 200, + "headers": { + "Link": "; rel=\"next\"" + } + }, + "list_groups_context2": { + "method": "GET", + "endpoint": "courses/1/groups?page=2&per_page=2", + "data": [ + { + "id": 3, + "name": "Group 3" + }, + { + "id": 4, + "name": "Group 4" + } + ], + "status_code": 200 } } \ No newline at end of file diff --git a/tests/fixtures/user.json b/tests/fixtures/user.json index 1754dafa..8646023e 100644 --- a/tests/fixtures/user.json +++ b/tests/fixtures/user.json @@ -270,8 +270,8 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" - } + "Link": "; rel=\"next\"" + } }, "list_enrollments_2": { "method": "GET", @@ -288,6 +288,39 @@ ], "status_code": 200 }, + "list_groups": { + "method": "GET", + "endpoint": "users/self/groups", + "data": [ + { + "id": 1, + "name": "Group 1" + }, + { + "id": 2, + "name": "Group 2" + } + ], + "status_code": 200, + "headers": { + "Link": "; rel=\"next\"" + } + }, + "list_groups2": { + "method": "GET", + "endpoint": "users/self/groups?page=2&per_page=2", + "data": [ + { + "id": 3, + "name": "Group 3" + }, + { + "id": 4, + "name": "Group 4" + } + ], + "status_code": 200 + }, "merge": { "method": "PUT", "endpoint": "users/1/merge_into/2", @@ -440,16 +473,16 @@ "data": { "upload_url": "mock://example.com/api/v1/files/upload_response_upload_url", "upload_params": { - "some_param": "param123", - "a_different_param": "param456" + "some_param": "param123", + "a_different_param": "param456" } } }, "upload_final": { "method": "POST", - "endpoint": "files/upload_response_upload_url", - "data": { - "url": "great_url_success" - } + "endpoint": "files/upload_response_upload_url", + "data": { + "url": "great_url_success" + } } } diff --git a/tests/test_account.py b/tests/test_account.py index dd107a64..e89c8a1b 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -10,6 +10,7 @@ from pycanvas.enrollment import Enrollment from pycanvas.external_tool import ExternalTool from pycanvas.exceptions import RequiredFieldMissing +from pycanvas.group import Group from pycanvas.user import User from util import register_uris @@ -26,10 +27,10 @@ def setUpClass(self): 'create_notification', 'create_subaccount', 'create_user', 'delete_user', 'enroll_by_id', 'get_by_id', 'get_by_id_2', 'get_by_id_3', 'get_courses', 'get_courses_page_2', - 'get_external_tools', 'get_external_tools_p2', 'reports', - 'reports_page_2', 'report_index', 'report_index_page_2', - 'subaccounts', 'subaccounts_page_2', 'users', 'users_page_2', - 'user_notifs', 'user_notifs_page_2', 'update', 'update_fail' + 'get_external_tools', 'get_external_tools_p2', 'list_groups_context', + 'list_groups_context2', 'reports', 'reports_page_2', 'report_index', + 'report_index_page_2', 'subaccounts', 'subaccounts_page_2', 'users', + 'users_page_2', 'user_notifs', 'user_notifs_page_2', 'update', 'update_fail' ], 'external_tool': ['get_by_id_account'], 'user': ['get_by_id'], @@ -242,3 +243,10 @@ def test_enroll_by_id(self): target_enrollment = self.account.enroll_by_id(1) assert isinstance(target_enrollment, Enrollment) + + def test_list_groups_in_context(self): + groups = self.account.list_groups_in_context() + group_list = [group for group in groups] + + assert isinstance(group_list[0], Group) + assert len(group_list) == 4 diff --git a/tests/test_course.py b/tests/test_course.py index 9a3d86df..2586bf8f 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -13,6 +13,7 @@ from pycanvas.enrollment import Enrollment from pycanvas.external_tool import ExternalTool from pycanvas.exceptions import ResourceDoesNotExist, RequiredFieldMissing +from pycanvas.group import Group from pycanvas.module import Module from pycanvas.quiz import Quiz from pycanvas.section import Section @@ -33,9 +34,9 @@ def setUpClass(self): 'get_pages', 'get_pages2', 'get_quiz', 'get_recent_students', 'get_recent_students_p2', 'get_section', 'get_user', 'get_user_id_type', 'get_users', 'get_users_p2', - 'list_enrollments', 'list_enrollments_2', 'list_modules', + 'list_enrollments', 'list_enrollments_2', 'list_modules', 'list_groups_context', 'list_modules2', 'list_sections', 'list_sections2', 'list_quizzes', 'list_quizzes2', - 'preview_html', 'reactivate_enrollment', 'reset', 'settings', + 'list_groups_context2', 'preview_html', 'reactivate_enrollment', 'reset', 'settings', 'show_front_page', 'update', 'update_settings', 'upload', 'upload_final' ], @@ -386,6 +387,14 @@ def test_create_course_section(self): assert isinstance(section, Section) + def test_list_groups_in_context(self): + groups = self.course.list_groups_in_context() + group_list = [group for group in groups] + + assert isinstance(group_list[0], Group) + assert len(group_list) == 4 + + class TestCourseNickname(unittest.TestCase): """ diff --git a/tests/test_user.py b/tests/test_user.py index 178d64a5..2493d901 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -10,6 +10,7 @@ from pycanvas.assignment import Assignment from pycanvas.avatar import Avatar from pycanvas.course import Course +from pycanvas.group import Group from pycanvas.enrollment import Enrollment from pycanvas.page_view import PageView from pycanvas.user import User @@ -27,7 +28,7 @@ def setUpClass(self): 'avatars', 'avatars_p2', 'color', 'color_update', 'colors', 'courses', 'courses_p2', 'edit', 'get_by_id', 'get_by_id_2', 'get_user_assignments', 'get_user_assignments2', - 'list_enrollments', 'list_enrollments_2', 'merge', + 'list_enrollments', 'list_enrollments_2', 'list_groups', 'list_groups2', 'merge', 'missing_sub', 'missing_sub_p2', 'page_views', 'page_views_p2', 'profile', 'update_settings', 'upload', 'upload_final' ] @@ -193,3 +194,10 @@ def test_upload(self): os.remove(filename) except OSError: pass + + def test_list_groups(self): + groups = self.user.list_groups() + group_list = [group for group in groups] + + assert len(group_list) == 4 + assert isinstance(group_list[0], Group) From d268afc2e42988e05b50e9ee4e880639daaafb1c Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Tue, 23 Aug 2016 16:05:56 -0400 Subject: [PATCH 094/145] QA'd roles, tested on local canvas version --- .gitignore | 1 + pycanvas/account.py | 19 ++++++++----------- tests/test_account.py | 25 ++++++------------------- tests/test_role.py | 34 ---------------------------------- 4 files changed, 15 insertions(+), 64 deletions(-) delete mode 100644 tests/test_role.py diff --git a/.gitignore b/.gitignore index 02fb63d7..ef3b71d3 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ _build/ _static/ _templates/ _test.py +_setup.py diff --git a/pycanvas/account.py b/pycanvas/account.py index 27bac668..5540dd35 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -346,7 +346,7 @@ def update(self, **kwargs): else: return False - def get_roles(self, **kwargs): + def list_roles(self, **kwargs): """ List the roles available to an account. @@ -355,7 +355,6 @@ def get_roles(self, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.role.Role` """ - from role import Role return PaginatedList( Role, @@ -376,7 +375,6 @@ def get_role(self, role_id): :type role_id: int :rtype: :class:`pycanvas.role.Role` """ - from role import Role response = self._requester.request( 'GET', @@ -395,16 +393,16 @@ def create_role(self, label, **kwargs): :type label: str :rtype: :class:`pycanvas.role.Role` """ - from role import Role response = self._requester.request( 'POST', 'accounts/%s/roles' % (self.id), + label=label, **combine_kwargs(**kwargs) ) return Role(self._requester, response.json()) - def deactivate_role(self, role_id): + def deactivate_role(self, role_id, **kwargs): """ Deactivate a custom role. @@ -415,15 +413,15 @@ def deactivate_role(self, role_id): :type role_id: int :rtype: :class:`pycanvas.role.Role` """ - from role import Role response = self._requester.request( 'DELETE', 'accounts/%s/roles/%s' % (self.id, role_id), + **combine_kwargs(**kwargs) ) return Role(self._requester, response.json()) - def activate_role(self, role_id): + def activate_role(self, role_id, **kwargs): """ Reactivate an inactive role. @@ -434,11 +432,11 @@ def activate_role(self, role_id): :type role_id: int :rtype: :class:`pycanvas.role.Role` """ - from role import Role response = self._requester.request( 'POST', - 'accounts/%s/roles/%s/activate' % (self.id, role_id) + 'accounts/%s/roles/%s/activate' % (self.id, role_id), + **combine_kwargs(**kwargs) ) return Role(self._requester, response.json()) @@ -453,7 +451,6 @@ def update_role(self, role_id, **kwargs): :type role_id: int :rtype: :class:`pycanvas.role.Role` """ - from role import Role response = self._requester.request( 'PUT', @@ -501,5 +498,5 @@ def __str__(self): # pragma: no cover class Role(CanvasObject): - def __str__(self): + def __str__(self): # pragma: no cover return "id: %s" % (self.id) diff --git a/tests/test_account.py b/tests/test_account.py index a1609112..266d6216 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -5,8 +5,7 @@ import settings from pycanvas import Canvas -from pycanvas.account import Account, AccountNotification, AccountReport -from pycanvas.role import Role +from pycanvas.account import Account, AccountNotification, AccountReport, Role from pycanvas.course import Course from pycanvas.enrollment import Enrollment from pycanvas.external_tool import ExternalTool @@ -23,13 +22,13 @@ class TestAccount(unittest.TestCase): def setUpClass(self): requires = { 'account': [ -<<<<<<< HEAD 'activate_role', 'close_notification', 'create', 'create_course', 'create_2', 'create_notification', 'create_role', 'create_subaccount', 'create_user', 'deactivate_role', 'delete_user', 'enroll_by_id', 'get_by_id', 'get_by_id_2', 'get_by_id_3', - 'get_courses', 'get_courses_page_2', 'get_role', + 'get_courses', 'get_courses_page_2', + 'get_external_tools', 'get_external_tools_p2', 'get_role', 'list_roles', 'list_roles_2', 'reports', 'reports_page_2', 'report_index', 'report_index_page_2', 'subaccounts', @@ -38,20 +37,8 @@ def setUpClass(self): 'update_fail', 'update_role' ], 'generic': ['not_found'], - 'user': ['get_by_id'] -======= - 'close_notification', 'create', 'create_2', 'create_course', - 'create_notification', 'create_subaccount', 'create_user', - 'delete_user', 'enroll_by_id', 'get_by_id', 'get_by_id_2', - 'get_by_id_3', 'get_courses', 'get_courses_page_2', - 'get_external_tools', 'get_external_tools_p2', 'reports', - 'reports_page_2', 'report_index', 'report_index_page_2', - 'subaccounts', 'subaccounts_page_2', 'users', 'users_page_2', - 'user_notifs', 'user_notifs_page_2', 'update', 'update_fail' - ], - 'external_tool': ['get_by_id_account'], 'user': ['get_by_id'], ->>>>>>> 62265f63cc518271723f8192c295e3f6d7b9a1a7 + 'external_tool': ['get_by_id_account'] } adapter = requests_mock.Adapter() @@ -256,8 +243,8 @@ def test_update_fail(self): success = account.update(account=update_account_dict) assert not success - def test_get_roles(self): - roles = self.account.get_roles() + def test_list_roles(self): + roles = self.account.list_roles() role_list = [role for role in roles] assert len(role_list) == 4 diff --git a/tests/test_role.py b/tests/test_role.py deleted file mode 100644 index 1c7b9650..00000000 --- a/tests/test_role.py +++ /dev/null @@ -1,34 +0,0 @@ - -import unittest -import requests_mock -import settings - -from pycanvas import Canvas -from pycanvas.role import Role -from pycanvas.account import Account -from util import register_uris - - -class TestRole(unittest.TestCase): - """ - Tests Role methods. - """ - @classmethod - def setUpClass(self): - requires = { - 'account': ['get_by_id','get_role'], - 'generic': ['not_found'], - 'user': ['get_by_id'] - } - - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) - - self.account = self.canvas.get_account(1) - self.role = self.account.get_role(2) - - # __str__() - def test__str__(self): - string = str(self.role) - assert isinstance(string, str) From e1d52fc8d172793aa87bd7d12091ac693c3f4471 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Tue, 23 Aug 2016 16:13:30 -0400 Subject: [PATCH 095/145] cleaned gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index ef3b71d3..02fb63d7 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,3 @@ _build/ _static/ _templates/ _test.py -_setup.py From ec7f070f2ec0444556aeeeb6bd8b6cdef96d4333 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Tue, 23 Aug 2016 16:46:07 -0400 Subject: [PATCH 096/145] Added more account objects to docs --- docs/account-ref.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/account-ref.rst b/docs/account-ref.rst index c90cb33b..e5e3ac31 100644 --- a/docs/account-ref.rst +++ b/docs/account-ref.rst @@ -3,4 +3,13 @@ Account ======= .. autoclass:: pycanvas.account.Account - :members: \ No newline at end of file + :members: + +.. autoclass:: pycanvas.account.AccountNotification + :members: + +.. autoclass:: pycanvas.account.AccountReport + :members: + +.. autoclass:: pycanvas.account.Role + :members: From cf087b57c09c7043cc430e1eb07451b33db96f3a Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Wed, 24 Aug 2016 09:39:59 -0400 Subject: [PATCH 097/145] made fixes based on comments --- pycanvas/account.py | 12 ++++++------ pycanvas/exceptions.py | 2 +- tests/test_account.py | 5 ++--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index 5540dd35..e28ce221 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -353,7 +353,7 @@ def list_roles(self, **kwargs): :calls: `GET /api/v1/accounts/:account_id/roles \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.role.Role` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.account.Role` """ return PaginatedList( @@ -373,7 +373,7 @@ def get_role(self, role_id): :param role_id: The ID of the role. :type role_id: int - :rtype: :class:`pycanvas.role.Role` + :rtype: :class:`pycanvas.account.Role` """ response = self._requester.request( @@ -391,7 +391,7 @@ def create_role(self, label, **kwargs): :param label: The label for the role. :type label: str - :rtype: :class:`pycanvas.role.Role` + :rtype: :class:`pycanvas.account.Role` """ response = self._requester.request( @@ -411,7 +411,7 @@ def deactivate_role(self, role_id, **kwargs): :param role_id: The ID of the role. :type role_id: int - :rtype: :class:`pycanvas.role.Role` + :rtype: :class:`pycanvas.account.Role` """ response = self._requester.request( @@ -430,7 +430,7 @@ def activate_role(self, role_id, **kwargs): :param role_id: The ID of the role. :type role_id: int - :rtype: :class:`pycanvas.role.Role` + :rtype: :class:`pycanvas.account.Role` """ response = self._requester.request( @@ -449,7 +449,7 @@ def update_role(self, role_id, **kwargs): :param role_id: The ID of the role. :type role_id: int - :rtype: :class:`pycanvas.role.Role` + :rtype: :class:`pycanvas.account.Role` """ response = self._requester.request( diff --git a/pycanvas/exceptions.py b/pycanvas/exceptions.py index ce37c7c5..626dae13 100644 --- a/pycanvas/exceptions.py +++ b/pycanvas/exceptions.py @@ -15,7 +15,7 @@ def __init__(self, message): if errors: self.message = str(errors) else: - self.message = ('Something went wrong.', message) + self.message = ('Something went wrong. ', message) else: self.message = message diff --git a/tests/test_account.py b/tests/test_account.py index 266d6216..72cc7e1e 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -36,9 +36,8 @@ def setUpClass(self): 'user_notifs', 'user_notifs_page_2', 'update', 'update_fail', 'update_role' ], - 'generic': ['not_found'], - 'user': ['get_by_id'], - 'external_tool': ['get_by_id_account'] + 'external_tool': ['get_by_id_account'], + 'user': ['get_by_id'] } adapter = requests_mock.Adapter() From 563dbf66b51647d1cd65c6fbb03366bd33b6b2fc Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Wed, 21 Sep 2016 10:54:06 -0400 Subject: [PATCH 098/145] finished adding group functions to canvas.py and group.py, as well as making tests. Added GroupMembership functions to group.py and added tests. Beginning adding in GroupCategories --- pycanvas/canvas.py | 16 ++ pycanvas/group.py | 471 ++++++++++++++++++++++++++++++++++---- tests/fixtures/group.json | 325 ++++++++++++++++++++++++-- tests/test_canvas.py | 10 +- tests/test_group.py | 145 ++++++++++-- tests/test_page.py | 4 +- 6 files changed, 890 insertions(+), 81 deletions(-) diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index 6691457c..05954452 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -318,6 +318,22 @@ def search_accounts(self, **kwargs): ) return response.json() + def create_group(self, **kwargs): + """ + Create a group + + :calls: `POST /api/v1/groups/ \ + `_ + + :rtype: :class:`pycanvas.group.Group` + """ + response = self.__requester.request( + 'POST', + 'groups', + **combine_kwargs(**kwargs) + ) + return Group(self.__requester, response.json()) + def get_group(self, group_id, **kwargs): """ Return the data for a single group. If the caller does not diff --git a/pycanvas/group.py b/pycanvas/group.py index e5f429f1..004e2e7c 100644 --- a/pycanvas/group.py +++ b/pycanvas/group.py @@ -9,21 +9,31 @@ class Group(CanvasObject): def __str__(self): return "%s %s %s" % (self.id, self.name, self.description) - def show_front_page(self): + def create_page(self, wiki_page, **kwargs): """ - Retrieve the content of the front page. + Create a new wiki page. - :calls: `GET /api/v1/groups/:group_id/front_page \ - `_ + :calls: `POST /api/v1/groups/:group_id/pages \ + `_ - :rtype: :class:`pycanvas.group.Group` + :param title: The title for the page. + :type title: dict + :returns: The created page. + :rtype: :class: `pycanvas.page.Page` """ from course import Page + if isinstance(wiki_page, dict) and 'title' in wiki_page: + kwargs['wiki_page'] = wiki_page + else: + raise RequiredFieldMissing("Dictionary with key 'title' is required.") + response = self._requester.request( - 'GET', - 'groups/%s/front_page' % (self.id) + 'POST', + 'groups/%s/pages' % (self.id), + **combine_kwargs(**kwargs) ) + page_json = response.json() page_json.update({'group_id': self.id}) @@ -36,7 +46,7 @@ def edit_front_page(self, **kwargs): :calls: `PUT /api/v1/groups/:group_id/front_page \ `_ - :rtype: :class:`pycanvas.group.Group` + :rtype: :class:`pycanvas.page.Page` """ from course import Page @@ -50,6 +60,48 @@ def edit_front_page(self, **kwargs): return Page(self._requester, page_json) + def show_front_page(self): + """ + Retrieve the content of the front page. + + :calls: `GET /api/v1/groups/:group_id/front_page \ + `_ + + :rtype: :class:`pycanvas.group.Group` + """ + from course import Page + + response = self._requester.request( + 'GET', + 'groups/%s/front_page' % (self.id) + ) + page_json = response.json() + page_json.update({'group_id': self.id}) + + return Page(self._requester, page_json) + + def get_page(self, url): + """ + Retrieve the contents of a wiki page. + :calls: `GET /api/v1/groups/:group_id/pages/:url \ + `_ + + :param url: The url for the page. + :type url: string + :returns: The specified page. + :rtype: :class: `pycanvas.groups.Group` + """ + from course import Page + + response = self._requester.request( + 'GET', + 'groups/%s/pages/%s' % (self.id, url) + ) + page_json = response.json() + page_json.update({'group_id': self.id}) + + return Page(self._requester, page_json) + def get_pages(self, **kwargs): """ List the wiki pages associated with a group. @@ -57,7 +109,7 @@ def get_pages(self, **kwargs): :calls: `GET /api/v1/groups/:group_id/pages \ `_ - :rtype: :class:`pycanvas.groups.Group` + :rtype: :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.page.Page` """ from course import Page return PaginatedList( @@ -69,54 +121,395 @@ def get_pages(self, **kwargs): **combine_kwargs(**kwargs) ) - def create_page(self, wiki_page, **kwargs): + def edit(self, **kwargs): """ - Create a new wiki page. + Edit a group. - :calls: `POST /api/v1/groups/:group_id/pages \ - `_ + :calls: `PUT /api/v1/groups/:group_id \ + `_ - :param title: The title for the page. - :type title: dict - :returns: The created page. - :rtype: :class: `pycanvas.groups.Group` + :rtype: :class:`pycanvas.group.Group` """ - from course import Page + response = self._requester.request( + 'PUT', + 'groups/%s' % (self.id), + **combine_kwargs(**kwargs) + ) + return Group(self._requester, response.json()) - if isinstance(wiki_page, dict) and 'title' in wiki_page: - kwargs['wiki_page'] = wiki_page - else: - raise RequiredFieldMissing("Dictionary with key 'title' is required.") + def delete(self): + """ + Delete a group. + + :calls: `DELETE /api/v1/groups/:group_id \ + `_ + :rtype: :class:`pycanvas.group.Group` + """ response = self._requester.request( + 'DELETE', + 'groups/%s' % (self.id) + ) + return Group(self._requester, response.json()) + + def invite(self, invitees): + """ + Invite users to group. + + :calls: `POST /api/v1/groups/:group_id/invite \ + `_ + + :param invitees: list of user ids + :type invitees: integer list + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.groupGroupMembership` + """ + return PaginatedList( + GroupMembership, + self._requester, 'POST', - 'groups/%s/pages' % (self.id), + 'groups/%s/invite' % (self.id), + invitees=invitees + ) + + def list_users(self, **kwargs): + """ + List users in a group. + + :calls: `POST /api/v1/groups/:group_id/users \ + `_ + + :param invitees: list of user ids + :type invitees: integer list + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.user.User` + """ + from user import User + return PaginatedList( + User, + self._requester, + 'GET', + 'groups/%s/users' % (self.id), **combine_kwargs(**kwargs) ) - page_json = response.json() - page_json.update({'group_id': self.id}) + def remove_user(self, user): + """ + Leave a group if allowed. - return Page(self._requester, page_json) + :calls: `DELETE /api/v1/groups/:group_id/:type/:id \ + `_ - def get_page(self, url): + :param user: The user object or ID to remove from the group. + :type user: :class:`pycanvas.user.User` or int + + :rtype: :class:`pycanvas.user.User` """ - Retrieve the contents of a wiki page. - :calls: `GET /api/v1/groups/:group_id/pages/:url \ - `_ + from user import User + from util import obj_or_id - :param url: The url for the page. - :type url: string - :returns: The specified page. - :rtype: :class: `pycanvas.groups.Group` + user_id = obj_or_id(user, "user", (User,)) + + response = self._requester.request( + 'DELETE', + 'groups/%s/users/%s' % (self.id, user_id), + ) + return User(self._requester, response.json()) + + def upload(self, file, **kwargs): """ - from course import Page + Upload a file to the group. + Only those with the 'Manage Files' permission on a group can upload files to the group. + By default, this is anybody participating in the group, or any admin over the group. + + :calls: `POST /api/v1/groups/:group_id/files \ + `_ + + :param path: The path of the file to upload. + :type path: str + :param file: The file or path of the file to upload. + :type file: file or str + :returns: True if the file uploaded successfully, False otherwise, \ + and the JSON response from the API. + :rtype: tuple + """ + + from upload import Uploader + return Uploader( + self._requester, + 'groups/%s/files' % (self.id), + file, + **kwargs + ).start() + + def preview_html(self, html): + """ + Preview HTML content processed for this course. + + :calls: `POST /api/v1/groups/:group_id/preview_html \ + `_ + + :param html: The HTML code to preview. + :type html: str + :rtype: str + """ + response = self._requester.request( + 'POST', + 'groups/%s/preview_html' % (self.id), + html=html + ) + return response.json().get('html', '') + + # def get_activity_stream(self): + # """ + # Returns the current user's group-specific activity stream, paginated. + + # :calls: `GET /api/v1/groups/:group_id/activity_stream \ + # `_ + + # :rtype: list of various objects. + # """ + # response = self.__requester.request( + # 'GET', + # 'groups/self/activity_stream/' + # ) + # return response.json() + + def get_activity_stream_summary(self): + """ + Return a summary of the current user's global activity stream. + + :calls: `GET /api/v1/groups/:group_id/activity_stream/summary \ + `_ + + :rtype: dict + """ response = self._requester.request( 'GET', - 'groups/%s/pages/%s' % (self.id, url) + 'groups/%s/activity_stream/summary' % (self.id) ) - page_json = response.json() - page_json.update({'group_id': self.id}) + return response.json() - return Page(self._requester, page_json) + def list_memberships(self, **kwargs): + """ + List users in a group. + + :calls: `GET /api/v1/groups/:group_id/memberships \ + `_ + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.GroupMembership` + """ + return PaginatedList( + GroupMembership, + self._requester, + 'GET', + 'groups/%s/memberships' % (self.id), + **combine_kwargs(**kwargs) + ) + + def get_membership(self, user_id, membership_type): + """ + List users in a group. + + if membership_type = 'users' + :calls: `GET /api/v1/groups/:group_id/users/:user_id \ + `_ + + if membership_type = 'memberships' + :calls: `GET /api/v1/groups/:group_id/memberships/:membership_id \ + `_ + + :param invitees: list of user ids + :type invitees: integer list + + :rtype: :class:`pycanvas.group.GroupMembership` + """ + response = self._requester.request( + 'GET', + 'groups/%s/%s/%s' % (self.id, membership_type, user_id) + ) + return GroupMembership(self._requester, response.json()) + + def create_membership(self, user_id, **kwargs): + """ + Join, or request to join, a group, depending on the join_level of the group. + If the membership or join request already exists, then it is simply returned + + :calls: `POST /api/v1/groups/:group_id/memberships \ + `_ + + :rtype: :class:`pycanvas.group.GroupMembership` + """ + response = self._requester.request( + 'POST', + 'groups/%s/memberships' % (self.id), + user_id=user_id, + **combine_kwargs(**kwargs) + ) + return GroupMembership(self._requester, response.json()) + + def update_membership(self, user_id, **kwargs): + """ + Accept a membership request, or add/remove moderator rights. + + :calls: `PUT /api/v1/groups/:group_id/users/:user_id \ + `_ + + :rtype: :class:`pycanvas.group.GroupMembership` + """ + response = self._requester.request( + 'PUT', + 'groups/%s/users/%s' % (self.id, user_id), + **combine_kwargs(**kwargs) + ) + return GroupMembership(self._requester, response.json()) + + +class GroupMembership(CanvasObject): + + def __str__(self): + return "User: %s, Group: %s" % (self.user_id, self.group_id) + + def update(self, mem_id, **kwargs): + """ + Accept a membership request, or add/remove moderator rights. + + :calls: `PUT /api/v1/groups/:group_id/memberships/:membership_id \ + `_ + + :rtype: :class:`pycanvas.group.GroupMembership` + """ + response = self._requester.request( + 'PUT', + 'groups/%s/memberships/%s' % (self.id, mem_id), + **combine_kwargs(**kwargs) + ) + return GroupMembership(self._requester, response.json()) + + def remove_user(self, user): + """ + Remove user from membership. + + :calls: `DELETE /api/v1/groups/:group_id/:type/:id \ + `_ + + :param user: The user object or ID to remove from the group. + :type user: :class:`pycanvas.user.User` or int + + :rtype: unknown + """ + from user import User + from util import obj_or_id + + user_id = obj_or_id(user, "user", (User,)) + + response = self._requester.request( + 'DELETE', + 'groups/%s/users/%s' % (self.id, user_id), + ) + return response.json() + + def remove_self(self): + """ + Leave a group if allowed. + + :calls: `DELETE /api/v1/groups/:group_id/:type/:id \ + `_ + + :rtype: unknown + """ + response = self._requester.request( + 'DELETE', + 'groups/%s/memberships/self' % (self.id), + ) + return response.json() + + +class GroupCategories(CanvasObject): + # List group categories for a context + # GET /api/v1/accounts/:account_id/group_categories + # GET /api/v1/courses/:course_id/group_categories + # https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.index + + # Create a Group Category + # POST /api/v1/accounts/:account_id/group_categories + # POST /api/v1/courses/:course_id/group_categories + # https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.create + + def __str__(self): + return "id: %s, group_id: %s" % (self.id, self.group_id) + + def create_group(self, **kwargs): + """ + Create a group + + :calls: `POST /api/v1/group_categories/:group_category_id/groups \ + `_ + + :rtype: :class:`pycanvas.group.Group` + """ + response = self.__requester.request( + 'POST', + 'group_categories/%s/groups' % (self.id), + **combine_kwargs(**kwargs) + ) + return Group(self.__requester, response.json()) + + def get_category(): + """ + Get a single group category + + :calls: `GET /api/v1/group_categories/:group_category_id \ + `_ + """ + + return None + + def update(): + """ + Update a Group Category + :calls: `PUT /api/v1/group_categories/:group_category_id \ + `_ + """ + + return None + + def delete_category(): + """ + Delete a Group Category + :calls: `DELETE /api/v1/group_categories/:group_category_id \ + `_ + """ + + return None + +# List groups in group category +# GET /api/v1/group_categories/:group_category_id/groups +# https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.groups + def list_groups(): + """ + + """ + + return None + +# List users in group category +# GET /api/v1/group_categories/:group_category_id/users +# https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.users + def list_users(): + """ + + """ + + return None + +# Assign unassigned members +# POST /api/v1/group_categories/:group_category_id/assign_unassigned_members +# https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.assign_unassigned_members + def assign_members(): + """ + + """ + + return None diff --git a/tests/fixtures/group.json b/tests/fixtures/group.json index 8e5fe53c..18f6c584 100644 --- a/tests/fixtures/group.json +++ b/tests/fixtures/group.json @@ -1,15 +1,25 @@ { - "show_front_page": { + "canvas_create_group": { + "method": "POST", + "endpoint": "groups", + "data": { + "id": 1, + "name": "group0", + "description": "first group ever" + }, + "status_code": 200 + }, + "canvas_get_group": { "method": "GET", - "endpoint": "groups/1/front_page", - "data":{ + "endpoint": "groups/1", + "data": { "id": 1, - "url": "front-page", - "title": "Front Page" + "name": "group1", + "description": "best group ever" }, "status_code": 200 }, - "get_single_group": { + "pages_get_group": { "method": "GET", "endpoint": "groups/1", "data": { @@ -19,7 +29,27 @@ }, "status_code": 200 }, - "edit_front_page": { + "pages_get_page": { + "method": "GET", + "endpoint": "groups/1/pages/my-url", + "data": { + "id": 1, + "url": "my-url", + "title": "Awesome Page" + }, + "status_code": 200 + }, + "group_create_page": { + "method": "POST", + "endpoint": "groups/1/pages", + "data": { + "id": 2, + "url": "new-page", + "title": "New Page" + }, + "status_code": 200 + }, + "group_edit_front_page": { "method": "PUT", "endpoint": "groups/1/front_page", "data": { @@ -29,7 +59,27 @@ }, "status_code": 200 }, - "get_pages": { + "group_show_front_page": { + "method": "GET", + "endpoint": "groups/1/front_page", + "data":{ + "id": 1, + "url": "front-page", + "title": "Front Page" + }, + "status_code": 200 + }, + "group_get_page": { + "method": "GET", + "endpoint": "groups/1/pages/my-url", + "data": { + "id": 1, + "url": "my-url", + "title": "Awesome Page" + }, + "status_code": 200 + }, + "group_get_pages": { "method": "GET", "endpoint": "groups/1/pages", "data": [ @@ -49,7 +99,7 @@ "Link": "; rel=\"next\"" } }, - "get_pages2": { + "group_get_pages2": { "method": "GET", "endpoint": "groups/1/get_pages?page=2&per_page=2", "data": [ @@ -66,33 +116,264 @@ ], "status_code": 200 }, - "create_page": { + "group_edit": { + "method": "PUT", + "endpoint": "groups/1", + "data": { + "id": 1, + "name": "group-none", + "description": "New Group" + }, + "status_code": 200 + }, + "group_delete": { + "method": "DELETE", + "endpoint": "groups/1", + "data": { + "id": 1, + "name": "group-none", + "description": "most deleted group ever" + }, + "status_code": 200 + }, + "group_invite": { "method": "POST", - "endpoint": "groups/1/pages", + "endpoint": "groups/1/invite", + "data": [ + { + "id": 4, + "group_id": 1, + "workflow_state": "invited", + "user_id": 1, + "moderator": false, + "sis_import_id": null + }, + { + "id": 5, + "group_id": 1, + "workflow_state": "invited", + "user_id": 2, + "moderator": false, + "sis_import_id": null + } + ], + "status_code": 200 + }, + "group_list_users": { + "method": "GET", + "endpoint": "groups/1/users", + "data": [ + { + "id": 1, + "name": "Jack Doe" + }, + { + "id": 2, + "name": "Jim Doe" + } + ], + "headers": { + "Link": "; rel=\"next\"" + }, + "status_code": 200 + }, + "group_list_users_p2": { + "method": "GET", + "endpoint": "groups/1/users?page=2&per_page=2", + "data": [ + { + "id": 3, + "name": "John Smith" + }, + { + "id": 4, + "name": "Joe Smith" + } + ], + "status_code": 200 + }, + "group_remove_user": { + "method": "DELETE", + "endpoint": "groups/1/users/1", "data": { - "id": 2, - "url": "new-page", - "title": "New Page" + "id": 1, + "name": "Benjamin Test" }, "status_code": 200 }, - "get_page": { + "group_upload": { + "method": "POST", + "endpoint": "groups/1/files", + "data": { + "upload_url": "mock://example.com/api/v1/files/upload_response_upload_url", + "upload_params": { + "some_param": "param123", + "a_different_param": "param456" + } + } + }, + "group_upload_final": { + "method": "POST", + "endpoint": "files/upload_response_upload_url", + "data": { + "url": "great_url_success" + } + }, + "group_preview_processed_html": { + "method": "POST", + "endpoint": "groups/1/preview_html", + "data": { + "html": "

processed html

" + }, + "status_code": 200 + }, + "group_activity_stream": { + "method": "", + "endpoint": "", + "data": { + "info": "not yet implemented" + }, + "status_code": 404 + }, + "group_get_activity_stream_summary": { "method": "GET", - "endpoint": "groups/1/pages/my-url", + "endpoint": "groups/1/activity_stream/summary", + "data": [ + { + "type": "DiscussionTopic", + "unread_count": 2, + "count": 7 + }, + { + "type": "Conversation", + "unread_count": 0, + "count": 3 + } + ], + "status_code": 200 + }, + "group_list_memberships": { + "method": "GET", + "endpoint": "groups/1/memberships", + "data": [ + { + "id": 1, + "group_id": 1, + "user_id": 1, + "workflow_state": "accepted", + "moderator": true, + "just_created": true, + "sis_import_id": 1 + }, + { + "id": 2, + "group_id": 2, + "user_id": 2, + "workflow_state": "accepted", + "moderator": true, + "just_created": true, + "sis_import_id": 2 + } + ], + "headers": { + "Link": "; rel=\"next\"" + }, + "status_code": 200 + }, + "group_list_memberships_p2": { + "method": "GET", + "endpoint": "groups/1/memberships?page=2&per_page=2", + "data": [ + { + "id": 3, + "group_id": 3, + "user_id": 3, + "workflow_state": "accepted", + "moderator": true, + "just_created": true, + "sis_import_id": 3 + }, + { + "id": 4, + "group_id": 4, + "user_id": 4, + "workflow_state": "accepted", + "moderator": true, + "just_created": true, + "sis_import_id": 4 + } + ], + "status_code": 200 + }, + "group_get_membership": { + "method": "GET", + "endpoint": "groups/1/users/1", "data": { "id": 1, - "url": "my-url", - "title": "Awesome Page" + "group_id": 1, + "user_id": 1, + "workflow_state": "accepted", + "moderator": true, + "just_created": true, + "sis_import_id": 1 }, "status_code": 200 }, - "edit_page": { + "group_create_membership": { + "method": "POST", + "endpoint": "groups/1/memberships", + "data": { + "id": 1, + "group_id": 1, + "user_id": 1, + "workflow_state": "accepted", + "moderator": true, + "just_created": true, + "sis_import_id": 1 + }, + "status_code": 200 + }, + "group_update_membership": { "method": "PUT", - "endpoint": "groups/1/pages/my-url", + "endpoint": "groups/1/users/1", "data": { "id": 1, - "title": "New Page", - "url": "my-url" + "group_id": 1, + "user_id": 1, + "workflow_state": "accepted", + "moderator": true, + "just_created": true, + "sis_import_id": 1 + }, + "status_code": 200 + }, + "membership_update": { + "method": "PUT", + "endpoint": "groups/1/memberships/1", + "data": { + "id": 1, + "group_id": 1, + "user_id": 1, + "workflow_state": "accepted", + "moderator": true, + "just_created": true, + "sis_import_id": 1 + }, + "status_code": 200 + }, + "membership_remove_user": { + "method": "DELETE", + "endpoint": "groups/1/users/1", + "data": { + + }, + "status_code": 200 + }, + "membership_remove_self": { + "method": "DELETE", + "endpoint": "groups/1/memberships/self", + "data": { + }, "status_code": 200 } diff --git a/tests/test_canvas.py b/tests/test_canvas.py index a7eb9c8f..e0f868df 100644 --- a/tests/test_canvas.py +++ b/tests/test_canvas.py @@ -34,7 +34,7 @@ def setUpClass(self): 'get_by_id', 'multiple', 'multiple_page_2', 'start_at_date', 'unicode_encode_error' ], - 'group': ['get_single_group'], + 'group': ['canvas_create_group', 'canvas_get_group'], 'section': ['get_by_id'], 'user': [ 'activity_stream_summary', 'course_nickname', 'course_nickname_set', @@ -200,6 +200,14 @@ def test_section(self): assert isinstance(info, Section) + # create_group() + def test_create_group(self): + group = self.canvas.create_group() + + assert isinstance(group, Group) + assert hasattr(group, 'name') + assert hasattr(group, 'description') + # get_group() def test_get_group(self): group = self.canvas.get_group(1) diff --git a/tests/test_group.py b/tests/test_group.py index 0a1fee7a..e7680caf 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -5,6 +5,9 @@ import settings from util import register_uris from pycanvas import Canvas +from pycanvas.group import Group +from pycanvas.group import GroupMembership +from pycanvas.group import GroupCategories from pycanvas.course import Page from pycanvas.exceptions import RequiredFieldMissing @@ -18,9 +21,19 @@ def setUpClass(self): requires = { 'course': ['get_by_id', 'show_front_page'], 'group': [ - 'create_page', 'edit_page', 'show_front_page', - 'get_single_group', 'edit_front_page', - 'get_page', 'get_pages', 'get_pages2' + 'canvas_create_group', 'canvas_get_group', + 'group_create_page', 'group_edit_front_page', + 'group_show_front_page', 'group_get_page', + 'group_get_pages', 'group_get_pages2', + 'group_edit', 'group_delete', + 'group_list_users', 'group_list_users_p2', + 'group_invite', 'group_remove_user', + 'group_upload', 'group_upload_final', + 'group_preview_processed_html', 'group_get_activity_stream_summary', + 'group_list_memberships', 'group_list_memberships_p2', + 'group_get_membership', 'group_create_membership', + 'group_update_membership', + 'membership_update', 'membership_remove_user', 'membership_remove_self' ] } @@ -35,6 +48,7 @@ def setUpClass(self): self.course = self.canvas.get_course(1) self.group = self.canvas.get_group(1) + self.membership = self.group.get_membership(1, "users") self.page = self.group.get_page('my-url') # __str__() @@ -45,7 +59,6 @@ def test__str__(self): # show_front_page() def test_show_front_page(self): front_page = self.group.show_front_page() - assert isinstance(front_page, Page) assert hasattr(front_page, 'url') assert hasattr(front_page, 'title') @@ -53,7 +66,6 @@ def test_show_front_page(self): # create_front_page() def test_edit_front_page(self): new_front_page = self.group.edit_front_page() - assert isinstance(new_front_page, Page) assert hasattr(new_front_page, 'url') assert hasattr(new_front_page, 'title') @@ -62,7 +74,6 @@ def test_edit_front_page(self): def test_get_pages(self): pages = self.group.get_pages() page_list = [page for page in pages] - assert len(page_list) == 4 assert isinstance(page_list[0], Page) assert hasattr(page_list[0], 'group_id') @@ -72,7 +83,6 @@ def test_get_pages(self): def test_create_page(self): title = 'New Page' new_page = self.group.create_page(wiki_page={'title': title}) - assert isinstance(new_page, Page) assert hasattr(new_page, 'title') assert new_page.title == title @@ -87,17 +97,118 @@ def test_create_page_fail(self): def test_get_page(self): url = 'my-url' page = self.group.get_page(url) - assert isinstance(page, Page) # edit() def test_edit(self): - new_title = "New Page" - self.page.edit(page={'title': new_title}) - - assert isinstance(self.page, Page) - assert hasattr(self.page, 'title') - assert self.page.title == new_title - - # reset for future tests - self.page = self.group.get_page('my-url') + new_title = "New Group" + response = self.group.edit(description=new_title) + assert isinstance(response, Group) + assert hasattr(response, 'description') + assert response.description == new_title + + # # reset for future tests + # self.group = self.group.get_page('my-url') + + # delete() + def test_delete(self): + group = self.group.delete() + assert isinstance(group, Group) + assert hasattr(group, 'name') + assert hasattr(group, 'description') + + # invite() + def test_invite(self): + user_list = ["1", "2"] + response = self.group.invite(user_list) + gmembership_list = [groupmembership for groupmembership in response] + assert isinstance(gmembership_list[0], GroupMembership) + assert len(gmembership_list) == 2 + + # list_users() + def test_list_users(self): + from pycanvas.user import User + users = self.group.list_users() + user_list = [user for user in users] + assert isinstance(user_list[0], User) + assert len(user_list) == 4 + + # remove_user() + def test_remove_user(self): + from pycanvas.user import User + response = self.group.remove_user(1) + assert isinstance(response, User) + + # upload() + def test_upload(self): + import uuid + import os + filename = 'testfile_%s' % uuid.uuid4().hex + file = open(filename, 'w+') + response = self.group.upload(file) + assert response[0] is True + assert isinstance(response[1], dict) + assert 'url' in response[1] + # http://stackoverflow.com/a/10840586 + # Not as stupid as it looks. + try: + os.remove(filename) + except OSError: + pass + + # preview_processed_html() + def test_preview_processed_html(self): + html_str = "

processed html

" + response = self.group.preview_html(html_str) + assert response == html_str + + # # get_activity_stream() - not implemented + # def test_activity_stream(self): + # return None + + # get_activity_stream_summary() + def test_get_activity_stream_summary(self): + response = self.group.get_activity_stream_summary() + assert len(response) == 2 + assert 'type' in response[0] + + # list_memberships() + def test_list_memberships(self): + response = self.group.list_memberships() + membership_list = [membership for membership in response] + assert len(membership_list) == 4 + assert isinstance(membership_list[0], GroupMembership) + assert hasattr(membership_list[0], 'group_id') + + # get_membership() + def test_get_membership(self): + response = self.group.get_membership(1, "users") + assert isinstance(response, GroupMembership) + + # create_membership() + def test_create_membership(self): + response = self.group.create_membership(1) + assert isinstance(response, GroupMembership) + + # update_membership() + def test_update_membership(self): + response = self.group.update_membership(1) + assert isinstance(response, GroupMembership) + +################################################ + # membership.update() + def test_membership_update(self): + response = self.membership.update(mem_id=1, moderator=False) + assert isinstance(response, GroupMembership) + + # membership.remove_user() + def test_membership_remove_user(self): + response = self.membership.remove_user(1) + assert not response + + # membership.remove_self() + def test_membership_remove_self(self): + response = self.membership.remove_self() + assert not response + +################################################ diff --git a/tests/test_page.py b/tests/test_page.py index fc3a6f68..04809d02 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -18,7 +18,7 @@ class TestPage(unittest.TestCase): def setUpClass(self): requires = { 'course': ['get_by_id'], - 'group': ['get_single_group', 'get_page'], + 'group': ['pages_get_group', 'pages_get_page'], 'generic': ['not_found'], 'page': [ 'get_page', 'edit', 'delete_page', @@ -131,7 +131,7 @@ class TestPageRevision(unittest.TestCase): def setUpClass(self): requires = { 'course': ['get_by_id', 'get_page'], - 'group': ['get_single_group', 'get_page'], + 'group': ['pages_get_group', 'pages_get_page'], 'generic': ['not_found'], 'page': ['get_latest_rev_by_id', 'get_latest_rev_by_id_group'] } From 3a5f07ede2a7ab387c58bab9d0893fa42699dfd7 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Mon, 26 Sep 2016 16:17:31 -0400 Subject: [PATCH 099/145] added functions, tests, and json for group conversations and updated 'process' class to 'progress' to match the documentation. --- pycanvas/account.py | 38 +++++- pycanvas/canvas.py | 8 +- pycanvas/course.py | 36 ++++++ pycanvas/group.py | 119 +++++++++++------ pycanvas/process.py | 7 - pycanvas/progress.py | 22 ++++ tests/fixtures/account.json | 45 +++++++ tests/fixtures/course.json | 45 +++++++ tests/fixtures/group.json | 244 +++++++++++++++++++++++++++++++++++ tests/fixtures/progress.json | 20 +++ tests/test_account.py | 17 ++- tests/test_canvas.py | 4 +- tests/test_course.py | 15 ++- tests/test_group.py | 151 +++++++++++++++++++--- tests/test_progress.py | 43 ++++++ 15 files changed, 741 insertions(+), 73 deletions(-) delete mode 100644 pycanvas/process.py create mode 100644 pycanvas/progress.py create mode 100644 tests/fixtures/progress.json create mode 100644 tests/test_progress.py diff --git a/pycanvas/account.py b/pycanvas/account.py index e64a90d5..3ab67020 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -370,7 +370,7 @@ def list_groups_in_context(self, **kwargs): """ Return list of active groups for the specified account. - :calls:`GET /api/v1/accounts/:account_id/groups \ + :calls: `GET /api/v1/accounts/:account_id/groups \ `_ :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.Group` @@ -384,6 +384,42 @@ def list_groups_in_context(self, **kwargs): **combine_kwargs(**kwargs) ) + def create_group_category(self, name, **kwargs): + """ + Create a Group Category + + :calls: `POST /api/v1/accounts/:account_id/group_categories \ + `_ + + :rtype: :class:`pycanvas.group.GroupCategories` + """ + from group import GroupCategories + + response = self._requester.request( + 'POST', + 'accounts/%s/group_categories' % (self.id), + **combine_kwargs(**kwargs) + ) + return GroupCategories(self._requester, response.json()) + + def list_group_categories(self): + """ + List group categories for a context + + :calls: `GET /api/v1/accounts/:account_id/group_categories \ + `_ + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.GroupCategories` + """ + from group import GroupCategories + + return PaginatedList( + GroupCategories, + self._requester, + 'GET', + 'accounts/%s/group_categories' % (self.id) + ) + class AccountNotification(CanvasObject): def __str__(self): # pragma: no cover diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index 05954452..7e31ad31 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -480,10 +480,10 @@ def conversations_batch_update(self, conversation_ids, event): :type conversation_ids: `list` of `str` :param event: The action to take on each conversation. :type event: `str` - :rtype: :class:`pycanvas.process.Process` + :rtype: :class:`pycanvas.progress.Progress` """ - from pycanvas.process import Process + from pycanvas.progress import Progress ALLOWED_EVENTS = [ 'mark_as_read', @@ -516,8 +516,8 @@ def conversations_batch_update(self, conversation_ids, event): event=event, **{"conversation_ids[]": conversation_ids} ) - return_process = Process(self.__requester, response.json()) - return return_process + return_progress = Progress(self.__requester, response.json()) + return return_progress except ValueError as e: return e diff --git a/pycanvas/course.py b/pycanvas/course.py index 6b2604a1..3089e726 100644 --- a/pycanvas/course.py +++ b/pycanvas/course.py @@ -738,6 +738,42 @@ def list_groups_in_context(self, **kwargs): **combine_kwargs(**kwargs) ) + def create_group_category(self, name, **kwargs): + """ + Create a Group Category + + :calls: `POST /api/v1/courses/:course_id/group_categories \ + `_ + + :rtype: :class:`pycanvas.group.GroupCategories` + """ + from pycanvas.group import Group, GroupCategories + + response = self._requester.request( + 'POST', + 'courses/%s/group_categories' % (self.id), + name=name, + **combine_kwargs(**kwargs) + ) + return GroupCategories(self._requester, response.json()) + + def list_group_categories(self): + """ + List group categories for a context + + :calls: `GET /api/v1/courses/:course_id/group_categories \ + `_ + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.GroupCategories` + """ + from pycanvas.group import Group, GroupCategories + + return PaginatedList( + GroupCategories, + self._requester, + 'GET', + 'courses/%s/group_categories' % (self.id) + ) class CourseNickname(CanvasObject): diff --git a/pycanvas/group.py b/pycanvas/group.py index 004e2e7c..3233d153 100644 --- a/pycanvas/group.py +++ b/pycanvas/group.py @@ -270,7 +270,7 @@ def preview_html(self, html): # :rtype: list of various objects. # """ - # response = self.__requester.request( + # response = self._requester.request( # 'GET', # 'groups/self/activity_stream/' # ) @@ -397,7 +397,7 @@ def remove_user(self, user): :param user: The user object or ID to remove from the group. :type user: :class:`pycanvas.user.User` or int - :rtype: unknown + :rtype: empty dict """ from user import User from util import obj_or_id @@ -417,7 +417,7 @@ def remove_self(self): :calls: `DELETE /api/v1/groups/:group_id/:type/:id \ `_ - :rtype: unknown + :rtype: empty dict """ response = self._requester.request( 'DELETE', @@ -427,18 +427,9 @@ def remove_self(self): class GroupCategories(CanvasObject): - # List group categories for a context - # GET /api/v1/accounts/:account_id/group_categories - # GET /api/v1/courses/:course_id/group_categories - # https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.index - - # Create a Group Category - # POST /api/v1/accounts/:account_id/group_categories - # POST /api/v1/courses/:course_id/group_categories - # https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.create def __str__(self): - return "id: %s, group_id: %s" % (self.id, self.group_id) + return "id: %s, name: %s" % (self.id, self.name) def create_group(self, **kwargs): """ @@ -449,67 +440,115 @@ def create_group(self, **kwargs): :rtype: :class:`pycanvas.group.Group` """ - response = self.__requester.request( + response = self._requester.request( 'POST', 'group_categories/%s/groups' % (self.id), **combine_kwargs(**kwargs) ) - return Group(self.__requester, response.json()) + return Group(self._requester, response.json()) - def get_category(): + def get_category(self, cat_id): """ Get a single group category :calls: `GET /api/v1/group_categories/:group_category_id \ `_ - """ - return None + :rtype: :class:`pycanvas.group.GroupCategories` + """ + response = self._requester.request( + 'GET', + 'group_categories/%s' % (cat_id) + ) + return GroupCategories(self._requester, response.json()) - def update(): + def update(self, **kwargs): """ Update a Group Category + :calls: `PUT /api/v1/group_categories/:group_category_id \ `_ - """ - return None + :rtype: :class:`pycanvas.group.GroupCategories` + """ + response = self._requester.request( + 'PUT', + 'group_categories/%s' % (self.id), + **combine_kwargs(**kwargs) + ) + return GroupCategories(self._requester, response.json()) - def delete_category(): + def delete(self): """ Delete a Group Category + :calls: `DELETE /api/v1/group_categories/:group_category_id \ `_ - """ - - return None -# List groups in group category -# GET /api/v1/group_categories/:group_category_id/groups -# https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.groups - def list_groups(): + :rtype: empty dict """ + response = self._requester.request( + 'DELETE', + 'group_categories/%s' % (self.id) + ) + return response.json() + def list_groups(self): """ + List groups in group category - return None + :calls: `GET /api/v1/group_categories/:group_category_id/groups \ + `_ -# List users in group category -# GET /api/v1/group_categories/:group_category_id/users -# https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.users - def list_users(): + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.Group` """ + return PaginatedList( + Group, + self._requester, + 'GET', + 'group_categories/%s/groups' % (self.id) + ) + def list_users(self, **kwargs): """ + List users in group category - return None + :calls: `GET /api/v1/group_categories/:group_category_id/users \ + `_ -# Assign unassigned members -# POST /api/v1/group_categories/:group_category_id/assign_unassigned_members -# https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.assign_unassigned_members - def assign_members(): + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.user.User` """ + from pycanvas.user import User + return PaginatedList( + User, + self._requester, + 'GET', + 'group_categories/%s/users' % (self.id), + **combine_kwargs(**kwargs) + ) + def assign_members(self, sync=False): """ + Assign unassigned members + + :calls: `POST /api/v1/group_categories/:group_category_id/assign_unassigned_members \ + `_ - return None + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.user.User` + or :class:`pycanvas.progress.Progress` + """ + from pycanvas.user import User + from pycanvas.progress import Progress + if sync: + return PaginatedList( + User, + self._requester, + 'POST', + 'group_categories/%s/assign_unassigned_members' % (self.id) + ) + else: + response = self._requester.request( + 'POST', + 'group_categories/%s/assign_unassigned_members' % (self.id) + ) + return Progress(self._requester, response.json()) diff --git a/pycanvas/process.py b/pycanvas/process.py deleted file mode 100644 index bb876ebf..00000000 --- a/pycanvas/process.py +++ /dev/null @@ -1,7 +0,0 @@ -from pycanvas.canvas_object import CanvasObject - - -class Process(CanvasObject): - - def __str__(self): # pragma: no cover - return "%s: %s, %s" % (self.id, self.tag, self.workflow_state) diff --git a/pycanvas/progress.py b/pycanvas/progress.py new file mode 100644 index 00000000..55b0916a --- /dev/null +++ b/pycanvas/progress.py @@ -0,0 +1,22 @@ +from pycanvas.canvas_object import CanvasObject + + +class Progress(CanvasObject): + + def __str__(self): # pragma: no cover + return "%s: %s, %s" % (self.id, self.tag, self.workflow_state) + + def query(self): + """ + Return completion and status information about an asynchronous job + + :calls: `GET /api/v1/progress/:id \ + `_ + + :rtype: :class:`pycanvas.progress.Progress` + """ + response = self._requester.request( + 'GET', + 'progress/%s' % (self.id) + ) + return Progress(self._requester, response.json()) diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json index 5240887b..c4c9d30d 100644 --- a/tests/fixtures/account.json +++ b/tests/fixtures/account.json @@ -542,5 +542,50 @@ "endpoint": "accounts/101", "data": {}, "status_code": 200 + }, + "create_group_category": { + "method": "POST", + "endpoint": "accounts/1/group_categories", + "data": { + "id": 1, + "name": "Shia Laboef", + "role": "communities", + "self_signup": null, + "auto_leader": null, + "context_type": "Account", + "account_id": 1, + "group_limit": null, + "progress": null + }, + "status_code": 200 + }, + "list_group_categories": { + "method": "GET", + "endpoint": "accounts/1/group_categories", + "data": [ + { + "id": 2, + "name": "Math Groups", + "role": "communities", + "self_signup": null, + "auto_leader": null, + "context_type": "Account", + "account_id": 2, + "group_limit": null, + "progress": null + }, + { + "id": 3, + "name": "Film Groups", + "role": "communities", + "self_signup": null, + "auto_leader": null, + "context_type": "Account", + "account_id": 3, + "group_limit": null, + "progress": null + } + ], + "status_code": 200 } } diff --git a/tests/fixtures/course.json b/tests/fixtures/course.json index a40abe51..7d643a95 100644 --- a/tests/fixtures/course.json +++ b/tests/fixtures/course.json @@ -750,5 +750,50 @@ } ], "status_code": 200 + }, + "create_group_category": { + "method": "POST", + "endpoint": "courses/1/group_categories", + "data": { + "id": 1, + "name": "Shia Laboef", + "role": "communities", + "self_signup": null, + "auto_leader": null, + "context_type": "Account", + "account_id": 1, + "group_limit": null, + "progress": null + }, + "status_code": 200 + }, + "list_group_categories": { + "method": "GET", + "endpoint": "courses/1/group_categories", + "data": [ + { + "id": 2, + "name": "Math Groups", + "role": "communities", + "self_signup": null, + "auto_leader": null, + "context_type": "Course", + "account_id": 2, + "group_limit": null, + "progress": null + }, + { + "id": 3, + "name": "Film Groups", + "role": "communities", + "self_signup": null, + "auto_leader": null, + "context_type": "Course", + "account_id": 3, + "group_limit": null, + "progress": null + } + ], + "status_code": 200 } } \ No newline at end of file diff --git a/tests/fixtures/group.json b/tests/fixtures/group.json index 18f6c584..0798d0f3 100644 --- a/tests/fixtures/group.json +++ b/tests/fixtures/group.json @@ -376,5 +376,249 @@ }, "status_code": 200 + }, + + + "categories_create_group": { + "method": "POST", + "endpoint": "group_categories/1/groups", + "data": { + "id": 1, + "name": "Test Create Group", + "description": null, + "is_public": false, + "followed_by_user": false, + "join_level": "invitation_only", + "members_count": 0, + "avatar_url": "https:///files/avatar_image.png", + "context_type": "Course", + "course_id": 3, + "role": null, + "group_category_id": 4, + "sis_group_id": "group4a", + "sis_import_id": 14, + "storage_quota_mb": 50, + "permissions": {"create_discussion_topic":true,"create_announcement":true} + }, + "status_code": 200 + }, + "categories_get_category": { + "method": "GET", + "endpoint": "group_categories/1", + "data": { + "id": 1, + "name": "Test Get Category", + "role": "communities", + "self_signup": null, + "auto_leader": null, + "context_type": "Account", + "account_id": 3, + "group_limit": null, + "progress": null + }, + "status_code": 200 + }, + "categories_update": { + "method": "PUT", + "endpoint": "group_categories/1", + "data": { + "id": 1, + "name": "Test Update Category", + "role": "communities", + "self_signup": null, + "auto_leader": null, + "context_type": "Account", + "account_id": 3, + "group_limit": null, + "progress": null + }, + "status_code": 200 + }, + "categories_delete_category": { + "method": "DELETE", + "endpoint": "group_categories/1", + "data": { + + }, + "status_code": 200 + }, + "categories_list_groups": { + "method": "GET", + "endpoint": "group_categories/1/groups", + "data": [ + { + "id": 1, + "name": "Math Group 1", + "description": null, + "is_public": false, + "followed_by_user": false, + "join_level": "invitation_only", + "members_count": 0, + "avatar_url": "https:///files/avatar_image.png", + "context_type": "Course", + "course_id": 3, + "role": null, + "group_category_id": 4, + "sis_group_id": "group4a", + "sis_import_id": 14, + "storage_quota_mb": 50, + "permissions": { + "create_discussion_topic":true, + "create_announcement":true + } + }, + { + "id": 2, + "name": "Math Group 1", + "description": null, + "is_public": false, + "followed_by_user": false, + "join_level": "invitation_only", + "members_count": 0, + "avatar_url": "https:///files/avatar_image.png", + "context_type": "Course", + "course_id": 3, + "role": null, + "group_category_id": 4, + "sis_group_id": "group4a", + "sis_import_id": 14, + "storage_quota_mb": 50, + "permissions": { + "create_discussion_topic":true, + "create_announcement":true + } + } + ], + "status_code": 200 + }, + "categories_list_users": { + "method": "GET", + "endpoint": "group_categories/1/users", + "data": [ + { + "user_id": 1, + "name": "Sam", + "display_name": "Sam", + "sections": [ + { + "section_id": 1, + "section_code": "Section 1" + } + ] + }, + { + "user_id": 2, + "name": "Sue", + "display_name": "Sue", + "sections": [ + { + "section_id": 2, + "section_code": "Section 2" + } + ] + }, + { + "user_id": 3, + "name": "Joe", + "display_name": "Joe", + "sections": [ + { + "section_id": 2, + "section_code": "Section 2" + } + ] + }, + { + "user_id": 4, + "name": "Cecil", + "display_name": "Cecil", + "sections": [ + { + "section_id": 3, + "section_code": "Section 3" + } + ] + } + ], + "status_code": 200 + }, + "categories_assign_members_true": { + "method": "POST", + "endpoint": "group_categories/1/assign_unassigned_members", + "data": [ + { + "id": 1, + "new_members": [ + { + "user_id": 1, + "name": "Sam", + "display_name": "Sam", + "sections": [ + { + "section_id": 1, + "section_code": "Section 1" + } + ] + }, + { + "user_id": 2, + "name": "Sue", + "display_name": "Sue", + "sections": [ + { + "section_id": 2, + "section_code": "Section 2" + } + ] + } + ] + }, + { + "id": 2, + "new_members": [ + { + "user_id": 3, + "name": "Joe", + "display_name": "Joe", + "sections": [ + { + "section_id": 2, + "section_code": "Section 2" + } + ] + }, + { + "user_id": 4, + "name": "Cecil", + "display_name": "Cecil", + "sections": [ + { + "section_id": 3, + "section_code": "Section 3" + } + ] + } + ] + } + ], + "status_code": 200 + }, + "categories_assign_members_false": { + "method": "POST", + "endpoint": "group_categories/1/assign_unassigned_members", + "data": { + "completion": 0, + "context_id": 20, + "context_type": "GroupCategory", + "created_at": "2013-07-05T10:57:48-06:00", + "id": 2, + "message": null, + "tag": "assign_unassigned_members", + "updated_at": "2013-07-05T10:57:48-06:00", + "user_id": null, + "workflow_state": "running", + "url": "http://localhost:3000/api/v1/progress/2" + }, + "status_code": 200 } } \ No newline at end of file diff --git a/tests/fixtures/progress.json b/tests/fixtures/progress.json new file mode 100644 index 00000000..8642747f --- /dev/null +++ b/tests/fixtures/progress.json @@ -0,0 +1,20 @@ +{ + "progress_query": { + "method": "GET", + "endpoint": "progress/2", + "data": { + "id": 2, + "context_id": 1, + "context_type": "Account", + "user_id": 123, + "tag": "assign_unassigned_members", + "completion": 100, + "workflow_state": "running", + "created_at": "2013-01-15T15:00:00Z", + "updated_at": "2013-01-15T15:04:00Z", + "message": "17 courses processed", + "url": "https://canvas.example.edu/api/v1/progress/1" + }, + "status_code": 200 + } +} \ No newline at end of file diff --git a/tests/test_account.py b/tests/test_account.py index e89c8a1b..88d175d2 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -10,7 +10,7 @@ from pycanvas.enrollment import Enrollment from pycanvas.external_tool import ExternalTool from pycanvas.exceptions import RequiredFieldMissing -from pycanvas.group import Group +from pycanvas.group import Group, GroupCategories from pycanvas.user import User from util import register_uris @@ -30,7 +30,8 @@ def setUpClass(self): 'get_external_tools', 'get_external_tools_p2', 'list_groups_context', 'list_groups_context2', 'reports', 'reports_page_2', 'report_index', 'report_index_page_2', 'subaccounts', 'subaccounts_page_2', 'users', - 'users_page_2', 'user_notifs', 'user_notifs_page_2', 'update', 'update_fail' + 'users_page_2', 'user_notifs', 'user_notifs_page_2', 'update', 'update_fail', + 'create_group_category', 'list_group_categories' ], 'external_tool': ['get_by_id_account'], 'user': ['get_by_id'], @@ -250,3 +251,15 @@ def test_list_groups_in_context(self): assert isinstance(group_list[0], Group) assert len(group_list) == 4 + + # create_group_category() + def test_create_group_category(self): + name_str = "Shia Laboef" + response = self.account.create_group_category(name=name_str) + assert isinstance(response, GroupCategories) + + # list_group_categories() + def test_list_group_categories(self): + response = self.account.list_group_categories() + category_list = [category for category in response] + assert isinstance(category_list[0], GroupCategories) diff --git a/tests/test_canvas.py b/tests/test_canvas.py index e0f868df..aa687b49 100644 --- a/tests/test_canvas.py +++ b/tests/test_canvas.py @@ -261,11 +261,11 @@ def test_conversations_get_running_batches(self): # batch_update() def test_conversations_batch_update(self): - from pycanvas.process import Process + from pycanvas.progress import Progress conversation_ids = [1, 2] this_event = "mark_as_read" result = self.canvas.conversations_batch_update(event=this_event, conversation_ids=conversation_ids) - assert isinstance(result, Process) + assert isinstance(result, Progress) def test_conversations_batch_updated_fail_on_event(self): conversation_ids = [1, 2] diff --git a/tests/test_course.py b/tests/test_course.py index 2586bf8f..5f4d636c 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -13,7 +13,7 @@ from pycanvas.enrollment import Enrollment from pycanvas.external_tool import ExternalTool from pycanvas.exceptions import ResourceDoesNotExist, RequiredFieldMissing -from pycanvas.group import Group +from pycanvas.group import Group, GroupCategories from pycanvas.module import Module from pycanvas.quiz import Quiz from pycanvas.section import Section @@ -38,7 +38,7 @@ def setUpClass(self): 'list_modules2', 'list_sections', 'list_sections2', 'list_quizzes', 'list_quizzes2', 'list_groups_context2', 'preview_html', 'reactivate_enrollment', 'reset', 'settings', 'show_front_page', 'update', 'update_settings', 'upload', - 'upload_final' + 'upload_final', 'create_group_category', 'list_group_categories' ], 'external_tool': ['get_by_id_course'], 'quiz': ['get_by_id'], @@ -394,6 +394,17 @@ def test_list_groups_in_context(self): assert isinstance(group_list[0], Group) assert len(group_list) == 4 + # create_group_category() + def test_create_group_category(self): + name_str = "Shia Laboef" + response = self.course.create_group_category(name=name_str) + assert isinstance(response, GroupCategories) + + # list_group_categories() + def test_list_group_categories(self): + response = self.course.list_group_categories() + category_list = [category for category in response] + assert isinstance(category_list[0], GroupCategories) class TestCourseNickname(unittest.TestCase): diff --git a/tests/test_group.py b/tests/test_group.py index e7680caf..abf1a07c 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -5,9 +5,7 @@ import settings from util import register_uris from pycanvas import Canvas -from pycanvas.group import Group -from pycanvas.group import GroupMembership -from pycanvas.group import GroupCategories +from pycanvas.group import Group, GroupMembership, GroupCategories from pycanvas.course import Page from pycanvas.exceptions import RequiredFieldMissing @@ -32,8 +30,7 @@ def setUpClass(self): 'group_preview_processed_html', 'group_get_activity_stream_summary', 'group_list_memberships', 'group_list_memberships_p2', 'group_get_membership', 'group_create_membership', - 'group_update_membership', - 'membership_update', 'membership_remove_user', 'membership_remove_self' + 'group_update_membership' ] } @@ -48,8 +45,6 @@ def setUpClass(self): self.course = self.canvas.get_course(1) self.group = self.canvas.get_group(1) - self.membership = self.group.get_membership(1, "users") - self.page = self.group.get_page('my-url') # __str__() def test__str__(self): @@ -195,20 +190,146 @@ def test_update_membership(self): response = self.group.update_membership(1) assert isinstance(response, GroupMembership) -################################################ - # membership.update() - def test_membership_update(self): + +class TestGroupMembership(unittest.TestCase): + """ + Tests GroupMembership functionality + """ + @classmethod + def setUpClass(self): + requires = { + 'group': [ + 'canvas_get_group', + 'group_get_membership', + 'membership_update', + 'membership_remove_user', + 'membership_remove_self' + ] + } + + require_generic = { + 'generic': ['not_found'] + } + + adapter = requests_mock.Adapter() + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) + register_uris(settings.BASE_URL, require_generic, adapter) + register_uris(settings.BASE_URL, requires, adapter) + + self.group = self.canvas.get_group(1) + self.membership = self.group.get_membership(1, "users") + + # __str__() + def test__str__(self): + string = str(self.membership) + assert isinstance(string, str) + + # update() + def test_update(self): response = self.membership.update(mem_id=1, moderator=False) assert isinstance(response, GroupMembership) - # membership.remove_user() - def test_membership_remove_user(self): + # remove_user() + def test_remove_user(self): response = self.membership.remove_user(1) assert not response - # membership.remove_self() - def test_membership_remove_self(self): + # remove_self() + def test_remove_self(self): response = self.membership.remove_self() assert not response -################################################ + +class TestGroupCategories(unittest.TestCase): + """ + Tests GroupCategories Item functionality + """ + @classmethod + def setUpClass(self): + requires = { + 'course': [ + 'get_by_id', 'create_group_category', 'list_group_categories' + ], + 'account': [ + 'get_by_id', 'create_group_category', 'list_group_categories' + ], + 'group': [ + 'categories_create_group', + 'categories_get_category', + 'categories_update', + 'categories_delete_category', + 'categories_list_groups', + 'categories_list_users', + 'categories_assign_members_true', + 'categories_assign_members_false' + ] + } + + require_generic = { + 'generic': ['not_found'] + } + + adapter = requests_mock.Adapter() + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) + register_uris(settings.BASE_URL, require_generic, adapter) + register_uris(settings.BASE_URL, requires, adapter) + + self.course = self.canvas.get_course(1) + self.group_category = self.course.create_group_category("Shia Laboef") + + # __str__() + def test__str__(self): + string = str(self.group_category) + assert isinstance(string, str) + + # create_group() + def test_create_group(self): + test_str = "Test Create Group" + response = self.group_category.create_group(name=test_str) + assert isinstance(response, Group) + assert hasattr(response, 'name') + assert response.name == test_str + + # get_category() + def test_get_category(self): + response = self.group_category.get_category(1) + assert isinstance(response, GroupCategories) + + # update() + def test_update(self): + new_name = "Test Update Category" + response = self.group_category.update(name=new_name) + assert isinstance(response, GroupCategories) + + # delete_category() + def test_delete_category(self): + response = self.group_category.delete() + return None + + # list_groups() + def test_list_groups(self): + response = self.group_category.list_groups() + group_list = [group for group in response] + assert len(group_list) == 2 + assert isinstance(group_list[0], Group) + assert hasattr(group_list[0], 'id') + + # list_users() + def test_list_users(self): + from pycanvas.user import User + response = self.group_category.list_users() + user_list = [user for user in response] + assert len(user_list) == 4 + assert isinstance(user_list[0], User) + assert hasattr(user_list[0], 'user_id') + + # assign_members() + def test_assign_members(self): + from pycanvas.progress import Progress + from pycanvas.paginated_list import PaginatedList + + result_true = self.group_category.assign_members(sync=True) + return_false = self.group_category.assign_members() + + assert isinstance(result_true, PaginatedList) + assert isinstance(return_false, Progress) diff --git a/tests/test_progress.py b/tests/test_progress.py new file mode 100644 index 00000000..ad393ef5 --- /dev/null +++ b/tests/test_progress.py @@ -0,0 +1,43 @@ +import unittest + +import requests_mock + +import settings +from pycanvas.canvas import Canvas +from pycanvas.course import Course +from pycanvas.group import Group, GroupCategories +from pycanvas.progress import Progress +from util import register_uris + + +class TestProgress(unittest.TestCase): + """ + Tests Progress functionality. + """ + @classmethod + def setUpClass(self): + requires = { + 'generic': ['not_found'], + 'course': ['get_by_id', 'create_group_category'], + 'group': ['categories_create_group', 'categories_assign_members_false'], + 'progress': ['progress_query'] + } + + adapter = requests_mock.Adapter() + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) + register_uris(settings.BASE_URL, requires, adapter) + + self.course = self.canvas.get_course(1) + self.group_category = self.course.create_group_category("Shia Laboef") + + self.progress = self.group_category.assign_members() + + # __str__() + def test__str__(self): + string = str(self.progress) + assert isinstance(string, str) + + # query() + def test_query(self): + response = self.progress.query() + assert isinstance(response, Progress) From 0fc0cb28d377510bcbd491789839e38681afaedb Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Mon, 7 Nov 2016 09:40:36 -0500 Subject: [PATCH 100/145] clean up commit --- pycanvas/group.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pycanvas/group.py b/pycanvas/group.py index 3233d153..908614d9 100644 --- a/pycanvas/group.py +++ b/pycanvas/group.py @@ -491,6 +491,7 @@ def delete(self): 'DELETE', 'group_categories/%s' % (self.id) ) + print response return response.json() def list_groups(self): From 568dee7a78a155a448c34459e9cff9dcf9730d9d Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Mon, 7 Nov 2016 11:14:02 -0500 Subject: [PATCH 101/145] Removing references to internal git server and email --- CONTRIBUTING.md | 16 ++++++++-------- README.md | 2 +- docs/getting-started.rst | 2 +- setup.py | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b8f1ec16..7cd8de43 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ Below you'll find guidelines for contributing that will keep our codebase clean ### Bug Reports #### Reporting bugs -Bug reports are awesome. Writing quality bug reports helps us identify issues and solve them even faster. You can submit bug reports directly to our [issue tracker](https://***REMOVED***/pycanvas/issues). +Bug reports are awesome. Writing quality bug reports helps us identify issues and solve them even faster. You can submit bug reports directly to our [issue tracker](https://example.com/changeme/pycanvas/issues). Here are a few things worth mentioning when making a report: @@ -34,11 +34,11 @@ Here are a few things worth mentioning when making a report: ### Resolving issues We welcome pull requests for bug fixes and new features! Feel free to browse our open, unassigned issues and assign yourself to them. You can also filter by labels: -* [simple](https://***REMOVED***/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=simple) -- easier issues to start working on; great for getting familiar with the codebase. -* [api coverage](https://***REMOVED***/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=api+coverage) -- covering new endpoints or updating existing ones. -* [internal](https://***REMOVED***/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=internal) -- updates to the engine to improve performance. -* [major](https://***REMOVED***/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=major) -- difficult or major changes or additions that require familiarity with the library. -* [bug](https://***REMOVED***/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=bug) -- happy little code accidents. +* [simple](https://example.com/changeme/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=simple) -- easier issues to start working on; great for getting familiar with the codebase. +* [api coverage](https://example.com/changeme/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=api+coverage) -- covering new endpoints or updating existing ones. +* [internal](https://example.com/changeme/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=internal) -- updates to the engine to improve performance. +* [major](https://example.com/changeme/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=major) -- difficult or major changes or additions that require familiarity with the library. +* [bug](https://example.com/changeme/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=bug) -- happy little code accidents. Once you've found an issue you're interested in tackling, take a look at our [first contribution tutorial](#making-your-first-contribution) for information on our pull request policy. @@ -49,7 +49,7 @@ Once you've found an issue you're interested in tackling, take a look at our [fi Now that you've selected an issue to work on, you'll need to set up an environment for writing code. We'll assume you already have pip, virtualenv, and git installed and are using a terminal. If not, please set those up before continuing. -1. Clone our repository by executing `git clone git@***REMOVED***/pycanvas.git` +1. Clone our repository by executing `git clone git@example.com:changeme/pycanvas.git` 2. Pull the latest commit from the **master** branch: `git pull origin master` 3. Create a new branch with the format **issue/[issue_number]-[issue-title]**: `git branch -b issue/1-test-issue-for-documentation` 4. Set up a new virtual environment ( `virtualenv env` ) and activate it (`source env/bin/activate`) @@ -179,7 +179,7 @@ pycanvas/util.py 22 0 100% TOTAL 629 0 100% ``` -Certain statements can be omitted from the coverage report by adding `# pragma: no cover` but this should be used conservatively. If your tests pass and your coverage is at 100%, you're ready to [submit a pull request](https://***REMOVED***/pycanvas/merge_requests)! +Certain statements can be omitted from the coverage report by adding `# pragma: no cover` but this should be used conservatively. If your tests pass and your coverage is at 100%, you're ready to [submit a pull request](https://example.com/changeme/pycanvas/merge_requests)! Be sure to include the issue number in the title with a pound sign in front of it (#123) so we know which issue the code is addressing. Point the branch at master and then submit it for review. diff --git a/README.md b/README.md index 65db52ac..de650f63 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ PyCanvas is a Python package that allows for simple access to the Instructure Ca ## Installation [internal only] -`pip install git+https://***REMOVED***/pycanvas.git@stable` +`pip install git+https://example.com/changeme/pycanvas.git@stable` ## Getting Started The first thing to do is open a connection with Canvas. You will need to provide the URL for the API endpoint of your Canvas instance as well as a valid API key. diff --git a/docs/getting-started.rst b/docs/getting-started.rst index cfa324be..23f8f5a2 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -6,7 +6,7 @@ Installing PyCanvas [SoonTM] You will eventually be able to install with pip:: - pip install git+ssh://git@***REMOVED***/pycanvas.git + pip install git+ssh://git@example.com:changeme/pycanvas.git Usage ----- diff --git a/setup.py b/setup.py index 2615463d..9c42ff7e 100644 --- a/setup.py +++ b/setup.py @@ -4,9 +4,9 @@ name='pycanvas', version='0.1.2', description='API wrapper for the Canvas LMS', - url='https://***REMOVED***/pycanvas/', + url='https://example.com/changeme/pycanvas/', author='Techrangers (University of Central Florida)', - author_email='***REMOVED***', + author_email='pycanvas@example.com', license='Some cool UCF license', packages=['pycanvas'], install_requires=['requests'], From 6950b03fc4deb1ef0b1e50a7f78034d1a4ab9616 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Fri, 18 Nov 2016 10:59:01 -0500 Subject: [PATCH 102/145] Updated combine_kwargs to combine multiple nested dictionaries. Added appropriate tests. --- pycanvas/util.py | 31 ++++--- tests/test_util.py | 200 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 200 insertions(+), 31 deletions(-) diff --git a/pycanvas/util.py b/pycanvas/util.py index 6ab41421..ed24ccc3 100644 --- a/pycanvas/util.py +++ b/pycanvas/util.py @@ -2,23 +2,32 @@ def combine_kwargs(**kwargs): - # TODO: look into implementing and testing multi-level post params - # e.g. `account[settings][restrict_student_future_view]` """ Combines a list of keyword arguments into a single dictionary. :rtype: dict """ - data = {} - for key, value in kwargs.iteritems(): - if isinstance(value, dict): - for subkey, subvalue in value.iteritems(): - data[key + '[' + subkey + ']'] = subvalue - continue - - data[key] = value - return data + def flatten_dict(prefix, key, value): + new_prefix = prefix + '[' + str(key) + ']' + if isinstance(value, dict): + d = {} + for k, v in value.iteritems(): + d.update(flatten_dict(new_prefix, k, v)) + return d + else: + return {new_prefix: value} + + combined_kwargs = {} + # Loop through all kwargs + for kw, arg in kwargs.iteritems(): + if isinstance(arg, dict): + # If the argument is a dictionary, flatten it. + for key, value in arg.iteritems(): + combined_kwargs.update(flatten_dict(str(kw), key, value)) + else: + combined_kwargs.update({str(kw): arg}) + return combined_kwargs def obj_or_id(parameter, param_name, object_types): diff --git a/tests/test_util.py b/tests/test_util.py index 9733d2bb..e62d35bb 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -26,29 +26,189 @@ def setUpClass(self): register_uris(settings.BASE_URL, requires, adapter) # combine_kwargs() - def test_combine_kwargs(self): - key1_dict = { - 'subkey1-1': 'value1', - 'subkey1-2': 'value2', - 'subkey1-3': 'value3', - } - key2_dict = { - 'subkey2-1': 'value4', - 'subkey2-2': 'value5', - 'subkey2-3': 'value6', - } - result = combine_kwargs(key1=key1_dict, key2=key2_dict) + def test_combine_kwargs_empty(self): + result = combine_kwargs() + self.assertIsInstance(result, dict) + self.assertEqual(result, {}) + + def test_combine_kwargs_single(self): + result = combine_kwargs(var='test') + self.assertIsInstance(result, dict) + self.assertTrue('var' in result) + self.assertEqual(result['var'], 'test') + + def test_combine_kwargs_single_dict(self): + result = combine_kwargs(var={'foo': 'bar'}) + self.assertIsInstance(result, dict) + self.assertTrue('var[foo]' in result) + self.assertEqual(result['var[foo]'], 'bar') + + def test_combine_kwargs_multiple_dicts(self): + result = combine_kwargs( + var1={'foo': 'bar'}, + var2={'fizz': 'buzz'} + ) + self.assertIsInstance(result, dict) + self.assertEqual(len(result), 2) + + self.assertIn('var1[foo]', result) + self.assertEqual(result['var1[foo]'], 'bar') + + self.assertIn('var2[fizz]', result) + self.assertEqual(result['var2[fizz]'], 'buzz') + + def test_combine_kwargs_multiple_mixed(self): + result = combine_kwargs( + var1=True, + var2={'fizz': 'buzz'}, + var3='foo', + var4=42, + ) + self.assertIsInstance(result, dict) + self.assertEqual(len(result), 4) + + expected_keys = ['var1', 'var2[fizz]', 'var3', 'var4'] + self.assertTrue(all(key in result for key in expected_keys)) + + self.assertEqual(result['var1'], True) + self.assertEqual(result['var2[fizz]'], 'buzz') + self.assertEqual(result['var3'], 'foo') + self.assertEqual(result['var4'], 42) + + def test_combine_kwargs_nested_dict(self): + result = combine_kwargs(dict={ + 'key': {'subkey': 'value'} + }) + self.assertIsInstance(result, dict) + self.assertEqual(len(result), 1) + + self.assertIn('dict[key][subkey]', result) + self.assertEqual(result['dict[key][subkey]'], 'value') + + def test_combine_kwargs_multiple_nested_dicts(self): + result = combine_kwargs( + dict1={ + 'key1': { + 'subkey1-1': 'value1-1', + 'subkey1-2': 'value1-2' + }, + 'key2': { + 'subkey2-1': 'value2-1', + 'subkey2-2': 'value2-2' + } + }, + dict2={ + 'key1': { + 'subkey1-1': 'value1-1', + 'subkey1-2': 'value1-2' + }, + 'key2': { + 'subkey2-1': 'value2-1', + 'subkey2-2': 'value2-2' + } + } + ) + self.assertIsInstance(result, dict) + self.assertEqual(len(result), 8) + + expected_keys = [ + 'dict1[key1][subkey1-1]', + 'dict1[key1][subkey1-2]', + 'dict1[key2][subkey2-1]', + 'dict1[key2][subkey2-2]', + 'dict2[key1][subkey1-1]', + 'dict2[key1][subkey1-2]', + 'dict2[key2][subkey2-1]', + 'dict2[key2][subkey2-2]', + ] + self.assertTrue(all(key in result for key in expected_keys)) + + self.assertEqual(result['dict1[key1][subkey1-1]'], 'value1-1') + self.assertEqual(result['dict1[key1][subkey1-2]'], 'value1-2') + self.assertEqual(result['dict1[key2][subkey2-1]'], 'value2-1') + self.assertEqual(result['dict1[key2][subkey2-2]'], 'value2-2') + self.assertEqual(result['dict2[key1][subkey1-1]'], 'value1-1') + self.assertEqual(result['dict2[key1][subkey1-2]'], 'value1-2') + self.assertEqual(result['dict2[key2][subkey2-1]'], 'value2-1') + self.assertEqual(result['dict2[key2][subkey2-2]'], 'value2-2') + + def test_combine_kwargs_super_nested_dict(self): + result = combine_kwargs( + big_dict={'a': {'b': {'c': {'d': {'e': 'We need to go deeper'}}}}} + ) + self.assertIsInstance(result, dict) + self.assertEqual(len(result), 1) + self.assertIn('big_dict[a][b][c][d][e]', result) + self.assertEqual(result['big_dict[a][b][c][d][e]'], 'We need to go deeper') + + def test_combine_kwargs_the_gauntlet(self): + result = combine_kwargs( + foo='bar', + fb={ + 3: 'fizz', + 5: 'buzz', + 15: 'fizzbuzz' + }, + true=False, + life=42, + days_of_xmas={ + 'first': { + 1: 'partridge in a pear tree' + }, + 'second': { + 1: 'partridge in a pear tree', + '2': 'turtle doves', + }, + 'third': { + 1: 'partridge in a pear tree', + '2': 'turtle doves', + 3: 'french hens' + }, + 'fourth': { + 1: 'partridge in a pear tree', + '2': 'turtle doves', + 3: 'french hens', + '4': 'mocking birds', + }, + 'fifth': { + 1: 'partridge in a pear tree', + '2': 'turtle doves', + 3: 'french hens', + '4': 'mocking birds', + '5': 'GOLDEN RINGS' + } + }, + super_nest={'1': {'2': {'3': {'4': {'5': {'6': 'tada'}}}}}} + ) + self.assertIsInstance(result, dict) + self.assertEqual(len(result), 22) - assert isinstance(result, dict) expected_keys = [ - 'key1[subkey1-1]', - 'key1[subkey1-2]', - 'key1[subkey1-3]', - 'key2[subkey2-1]', - 'key2[subkey2-2]', - 'key2[subkey2-3]' + 'foo', + 'fb[3]', + 'fb[5]', + 'fb[15]', + 'true', + 'life', + 'days_of_xmas[first][1]', + 'days_of_xmas[second][1]', + 'days_of_xmas[second][2]', + 'days_of_xmas[third][1]', + 'days_of_xmas[third][2]', + 'days_of_xmas[third][3]', + 'days_of_xmas[fourth][1]', + 'days_of_xmas[fourth][2]', + 'days_of_xmas[fourth][3]', + 'days_of_xmas[fourth][4]', + 'days_of_xmas[fifth][1]', + 'days_of_xmas[fifth][2]', + 'days_of_xmas[fifth][3]', + 'days_of_xmas[fifth][4]', + 'days_of_xmas[fifth][5]', + 'super_nest[1][2][3][4][5][6]' ] - assert all(key in result for key in expected_keys) + + self.assertTrue(all(key in result for key in expected_keys)) # obj_or_id() def test_obj_or_id_int(self): From 90da452c6ba9dca0d4c22e66e6b52147801c21f3 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Fri, 18 Nov 2016 12:39:18 -0500 Subject: [PATCH 103/145] Added kwargs to get_assignment and get assignments --- pycanvas/course.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pycanvas/course.py b/pycanvas/course.py index c05779fd..c68ea167 100644 --- a/pycanvas/course.py +++ b/pycanvas/course.py @@ -256,7 +256,8 @@ def get_enrollments(self, **kwargs): :calls: `GET /api/v1/courses/:course_id/enrollments \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.enrollment.Enrollment` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.enrollment.Enrollment` """ from enrollment import Enrollment return PaginatedList( @@ -267,7 +268,7 @@ def get_enrollments(self, **kwargs): **combine_kwargs(**kwargs) ) - def get_assignment(self, assignment_id): + def get_assignment(self, assignment_id, **kwargs): """ Return the assignment with the given ID. @@ -283,17 +284,19 @@ def get_assignment(self, assignment_id): response = self._requester.request( 'GET', 'courses/%s/assignments/%s' % (self.id, assignment_id), + **combine_kwargs(**kwargs) ) return Assignment(self._requester, response.json()) - def get_assignments(self): + def get_assignments(self, **kwargs): """ List all of the assignments in this course. :calls: `GET /api/v1/courses/:course_id/assignments \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.assignment.Assignment` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.assignment.Assignment` """ from assignment import Assignment @@ -301,7 +304,8 @@ def get_assignments(self): Assignment, self._requester, 'GET', - 'courses/%s/assignments' % (self.id) + 'courses/%s/assignments' % (self.id), + **combine_kwargs(**kwargs) ) def create_assignment(self, assignment, **kwargs): @@ -546,7 +550,8 @@ def get_external_tools(self, **kwargs): :calls: `GET /api/v1/courses/:course_id/external_tools \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.external_tool.ExternalTool` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.external_tool.ExternalTool` """ from external_tool import ExternalTool From d765020a23eded4c8ecb4552becae5ea219573a8 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Fri, 18 Nov 2016 16:37:12 -0500 Subject: [PATCH 104/145] Moved deactivate and reactivate enrollments to enrollments.py. Renamed enroll_by_id to get_enrollment. Updated tests. --- pycanvas/account.py | 2 +- pycanvas/course.py | 52 ---------------------------------- pycanvas/enrollment.py | 46 +++++++++++++++++++++++++++++- tests/fixtures/account.json | 11 ------- tests/fixtures/course.json | 38 ++++++++----------------- tests/fixtures/enrollment.json | 37 ++++++++++++++++++++++++ tests/test_account.py | 9 +++--- tests/test_course.py | 28 ++++-------------- tests/test_enrollment.py | 48 +++++++++++++++++++++++++++++++ 9 files changed, 154 insertions(+), 117 deletions(-) create mode 100644 tests/fixtures/enrollment.json create mode 100644 tests/test_enrollment.py diff --git a/pycanvas/account.py b/pycanvas/account.py index e28ce221..e6b04766 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -459,7 +459,7 @@ def update_role(self, role_id, **kwargs): ) return Role(self._requester, response.json()) - def enroll_by_id(self, enrollment_id, **kwargs): + def get_enrollment(self, enrollment_id, **kwargs): """ Get an enrollment object by ID. diff --git a/pycanvas/course.py b/pycanvas/course.py index c68ea167..a5c16391 100644 --- a/pycanvas/course.py +++ b/pycanvas/course.py @@ -475,58 +475,6 @@ def create_module(self, module, **kwargs): return Module(self._requester, module_json) - def deactivate_enrollment(self, enrollment_id, task): - """ - Delete, conclude, or deactivate an enrollment. - - The following tasks can be performed on an enrollment: conclude, delete, \ - inactivate, deactivate. - - :calls: `DELETE /api/v1/courses/:course_id/enrollments/:id \ - `_ - - :param enrollment_id: The ID of the enrollment to modify. - :type enrollment_id: int - :param task: The task to perform on the enrollment. - :type task: str - :rtype: :class:`pycanvas.enrollment.Enrollment` - """ - from enrollment import Enrollment - - ALLOWED_TASKS = ['conclude', 'delete', 'inactivate', 'deactivate'] - - if not task in ALLOWED_TASKS: - raise ValueError('%s is not a valid task. Please use one of the following: %s' % ( - task, - ','.join(ALLOWED_TASKS) - )) - - response = self._requester.request( - 'DELETE', - 'courses/%s/enrollments/%s' % (self.id, enrollment_id), - task=task - ) - return Enrollment(self._requester, response.json()) - - def reactivate_enrollment(self, enrollment_id): - """ - Activate an inactive enrollment. - - :calls: `PUT /api/v1/courses/:course_id/enrollments/:id/reactivate \ - `_ - - :param enrollment_id: The ID of the enrollment to reactivate. - :type enrollment_id: int - :rtype: :class:`pycanvas.enrollment.Enrollment` - """ - from enrollment import Enrollment - - response = self._requester.request( - 'PUT', - 'courses/%s/enrollments/%s/reactivate' % (self.id, enrollment_id) - ) - return Enrollment(self._requester, response.json()) - def get_external_tool(self, tool_id): """ :calls: `GET /api/v1/courses/:course_id/external_tools/:external_tool_id \ diff --git a/pycanvas/enrollment.py b/pycanvas/enrollment.py index 429b1211..978abbc1 100644 --- a/pycanvas/enrollment.py +++ b/pycanvas/enrollment.py @@ -3,10 +3,54 @@ class Enrollment(CanvasObject): - def __str__(self): # pragma: no cover + def __str__(self): return "id: %s, course_id: %s, user_id: %s, type: %s, " % ( self.id, self.course_id, self.user_id, self.type ) + + def deactivate(self, task): + """ + Delete, conclude, or deactivate an enrollment. + + The following tasks can be performed on an enrollment: conclude, delete, \ + inactivate, deactivate. + + :calls: `DELETE /api/v1/courses/:course_id/enrollments/:id \ + `_ + + :param task: The task to perform on the enrollment. + :type task: str + :rtype: :class:`pycanvas.enrollment.Enrollment` + """ + ALLOWED_TASKS = ['conclude', 'delete', 'inactivate', 'deactivate'] + + if task not in ALLOWED_TASKS: + raise ValueError('%s is not a valid task. Please use one of the following: %s' % ( + task, + ','.join(ALLOWED_TASKS) + )) + + response = self._requester.request( + 'DELETE', + 'courses/%s/enrollments/%s' % (self.course_id, self.id), + task=task + ) + return Enrollment(self._requester, response.json()) + + def reactivate(self): + """ + Activate an inactive enrollment. + + :calls: `PUT /api/v1/courses/:course_id/enrollments/:id/reactivate \ + `_ + + :rtype: :class:`pycanvas.enrollment.Enrollment` + """ + response = self._requester.request( + 'PUT', + 'courses/%s/enrollments/%s/reactivate' % (self.course_id, self.id) + ) + return Enrollment(self._requester, response.json()) diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json index 1374bac2..8c5e9dc8 100644 --- a/tests/fixtures/account.json +++ b/tests/fixtures/account.json @@ -108,17 +108,6 @@ } ] }, - "enroll_by_id": { - "method": "GET", - "endpoint": "accounts/1/enrollments/1", - "data": { - "id": 1, - "course_id": 1, - "user_id": 1, - "type": "StudentEnrollment" - }, - "status_code": 200 - }, "get_by_id": { "method": "GET", "endpoint": "accounts/1", diff --git a/tests/fixtures/course.json b/tests/fixtures/course.json index 6efa7db7..08effeba 100644 --- a/tests/fixtures/course.json +++ b/tests/fixtures/course.json @@ -19,17 +19,6 @@ }, "status_code": 200 }, - "deactivate_enrollment": { - "method": "DELETE", - "endpoint": "courses/1/enrollments/1", - "data": { - "id": 1, - "course_id": 1, - "user_id": 1, - "type": "StudentEnrollment" - }, - "status_code": 200 - }, "enroll_user": { "method": "POST", "endpoint": "courses/1/enrollments", @@ -284,11 +273,15 @@ "data": [ { "id": 1, - "course_id": 5 + "course_id": 1, + "user_id": 1, + "type": "StudentEnrollment" }, { "id": 2, - "course_id": 6 + "course_id": 1, + "user_id": 2, + "type": "TeacherEnrollment" } ], "status_code": 200, @@ -302,11 +295,15 @@ "data": [ { "id": 3, - "course_id": 7 + "course_id": 1, + "user_id": 10, + "type": "StudentEnrollment" }, { "id": 4, - "course_id": 8 + "course_id": 1, + "user_id": 8, + "type": "StudentEnrollment" } ], "status_code": 200 @@ -409,17 +406,6 @@ }, "status_code": 200 }, - "reactivate_enrollment": { - "method": "PUT", - "endpoint": "courses/1/enrollments/1/reactivate", - "data": { - "id": 1, - "course_id": 1, - "user_id": 1, - "type": "StudentEnrollment" - }, - "status_code": 200 - }, "reset": { "method": "POST", "endpoint": "courses/1/reset_content", diff --git a/tests/fixtures/enrollment.json b/tests/fixtures/enrollment.json new file mode 100644 index 00000000..d665daf6 --- /dev/null +++ b/tests/fixtures/enrollment.json @@ -0,0 +1,37 @@ +{ + "deactivate": { + "method": "DELETE", + "endpoint": "courses/1/enrollments/1", + "data": { + "id": 1, + "course_id": 1, + "user_id": 1, + "type": "StudentEnrollment", + "state": "inactive" + }, + "status_code": 200 + }, + "get_by_id": { + "method": "GET", + "endpoint": "accounts/1/enrollments/1", + "data": { + "id": 1, + "course_id": 1, + "user_id": 1, + "type": "StudentEnrollment" + }, + "status_code": 200 + }, + "reactivate": { + "method": "PUT", + "endpoint": "courses/1/enrollments/1/reactivate", + "data": { + "id": 1, + "course_id": 1, + "user_id": 1, + "type": "StudentEnrollment", + "state": "active" + }, + "status_code": 200 + } +} \ No newline at end of file diff --git a/tests/test_account.py b/tests/test_account.py index 72cc7e1e..701f916f 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -25,7 +25,7 @@ def setUpClass(self): 'activate_role', 'close_notification', 'create', 'create_course', 'create_2', 'create_notification', 'create_role', 'create_subaccount', 'create_user', - 'deactivate_role', 'delete_user', 'enroll_by_id', + 'deactivate_role', 'delete_user', 'get_by_id', 'get_by_id_2', 'get_by_id_3', 'get_courses', 'get_courses_page_2', 'get_external_tools', 'get_external_tools_p2', 'get_role', @@ -36,6 +36,7 @@ def setUpClass(self): 'user_notifs', 'user_notifs_page_2', 'update', 'update_fail', 'update_role' ], + 'enrollment': ['get_by_id'], 'external_tool': ['get_by_id_account'], 'user': ['get_by_id'] } @@ -286,8 +287,8 @@ def test_update_role(self): assert hasattr(updated_role, 'role') assert hasattr(updated_role, 'label') - # enroll_by_id() - def test_enroll_by_id(self): - target_enrollment = self.account.enroll_by_id(1) + # get_enrollment() + def test_get_enrollment(self): + target_enrollment = self.account.get_enrollment(1) assert isinstance(target_enrollment, Enrollment) diff --git a/tests/test_course.py b/tests/test_course.py index 9a3d86df..8665d1d0 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -25,8 +25,8 @@ class TestCourse(unittest.TestCase): def setUpClass(self): requires = { 'course': [ - 'create', 'create_assignment', 'create_section', 'create_module', 'create_page', - 'deactivate_enrollment', 'edit_front_page', 'enroll_user', + 'create', 'create_assignment', 'create_section', 'create_module', + 'create_page', 'edit_front_page', 'enroll_user', 'get_all_assignments', 'get_all_assignments2', 'get_assignment_by_id', 'get_by_id', 'get_external_tools', 'get_external_tools_p2', 'get_module_by_id', 'get_page', @@ -34,10 +34,10 @@ def setUpClass(self): 'get_recent_students_p2', 'get_section', 'get_user', 'get_user_id_type', 'get_users', 'get_users_p2', 'list_enrollments', 'list_enrollments_2', 'list_modules', - 'list_modules2', 'list_sections', 'list_sections2', 'list_quizzes', 'list_quizzes2', - 'preview_html', 'reactivate_enrollment', 'reset', 'settings', - 'show_front_page', 'update', 'update_settings', 'upload', - 'upload_final' + 'list_modules2', 'list_sections', 'list_sections2', + 'list_quizzes', 'list_quizzes2', 'preview_html', 'reset', + 'settings', 'show_front_page', 'update', 'update_settings', + 'upload', 'upload_final' ], 'external_tool': ['get_by_id_course'], 'quiz': ['get_by_id'], @@ -259,22 +259,6 @@ def test_get_enrollments(self): assert len(enrollment_list) == 4 assert isinstance(enrollment_list[0], Enrollment) - # deactivate_enrollment() - def test_deactivate_enrollment(self): - target_enrollment = self.course.deactivate_enrollment(1, 'conclude') - - assert isinstance(target_enrollment, Enrollment) - - def test_deactivate_enrollment_invalid_task(self): - with self.assertRaises(ValueError): - self.course.deactivate_enrollment(1, 'finish') - - # reactivate_enrollment() - def test_reactivate_enrollment(self): - target_enrollment = self.course.reactivate_enrollment(1) - - assert isinstance(target_enrollment, Enrollment) - # get_section def test_get_section(self): section = self.course.get_section(1) diff --git a/tests/test_enrollment.py b/tests/test_enrollment.py new file mode 100644 index 00000000..6247e2df --- /dev/null +++ b/tests/test_enrollment.py @@ -0,0 +1,48 @@ +import unittest + +import requests_mock + +import settings +from util import register_uris +from pycanvas.canvas import Canvas +from pycanvas.enrollment import Enrollment + + +class TestEnrollment(unittest.TestCase): + """ + Test Enrollment methods + """ + @classmethod + def setUpClass(self): + requires = { + 'account': ['get_by_id'], + 'generic': ['not_found'], + 'enrollment': ['deactivate', 'get_by_id', 'reactivate'] + } + adapter = requests_mock.Adapter() + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) + register_uris(settings.BASE_URL, requires, adapter) + + self.account = self.canvas.get_account(1) + self.enrollment = self.account.get_enrollment(1) + + # __str__() + def test__str__(self): + string = str(self.enrollment) + self.assertIsInstance(string, str) + + # deactivate() + def test_deactivate(self): + target_enrollment = self.enrollment.deactivate('conclude') + + self.assertIsInstance(target_enrollment, Enrollment) + + def test_deactivate_invalid_task(self): + with self.assertRaises(ValueError): + self.enrollment.deactivate('finish') + + # reactivate() + def test_reactivate(self): + target_enrollment = self.enrollment.reactivate() + + self.assertIsInstance(target_enrollment, Enrollment) From fec1d2d0f96841cd595c770b8c4554167e57e30a Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Fri, 18 Nov 2016 17:23:56 -0500 Subject: [PATCH 105/145] Minor text changes - fixing long line lengths --- pycanvas/account.py | 17 ++++++++++++----- pycanvas/conversation.py | 3 ++- pycanvas/module.py | 3 ++- pycanvas/page.py | 17 ++++++++++++++--- pycanvas/section.py | 3 ++- pycanvas/user.py | 6 ++++-- 6 files changed, 36 insertions(+), 13 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index e6b04766..49a2ce2b 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -134,7 +134,10 @@ def create_notification(self, account_notification, **kwargs): 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'.") + raise RequiredFieldMissing(( + "account_notification must be a dictionary with keys " + "'subject', 'message', 'start_at', and 'end_at'." + )) response = self._requester.request( 'POST', @@ -214,7 +217,8 @@ def get_external_tools(self, **kwargs): :calls: `GET /api/v1/accounts/:account_id/external_tools \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.external_tool.ExternalTool` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.external_tool.ExternalTool` """ from external_tool import ExternalTool @@ -236,7 +240,8 @@ def get_index_of_reports(self, report_type): :param report_type: The type of report. :type report_type: str - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.account.AccountReport` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.account.AccountReport` """ return PaginatedList( AccountReport, @@ -252,7 +257,8 @@ def get_reports(self): :calls: `GET /api/v1/accounts/:account_id/reports \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.account.AccountReport` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.account.AccountReport` """ return PaginatedList( AccountReport, @@ -311,7 +317,8 @@ def get_user_notifications(self, user): :param user: The user object or ID to retrieve notifications for. :type user: :class:`pycanvas.user.User` or int - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.account.AccountNotification` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.account.AccountNotification` """ from user import User diff --git a/pycanvas/conversation.py b/pycanvas/conversation.py index 46bf4c14..b894d50d 100644 --- a/pycanvas/conversation.py +++ b/pycanvas/conversation.py @@ -93,7 +93,8 @@ def delete_messages(self, remove): """ Delete messages from this conversation. Note that this only affects this user's view of the conversation. - If all messages are deleted, the conversation will be as well (equivalent to DELETE) by canvas + If all messages are deleted, the conversation will be as well + (equivalent to DELETE) by canvas :calls: `POST /api/v1/conversations/:id/remove_messages \ `_ diff --git a/pycanvas/module.py b/pycanvas/module.py index 0341cc3e..07afbc23 100644 --- a/pycanvas/module.py +++ b/pycanvas/module.py @@ -78,7 +78,8 @@ def list_module_items(self, **kwargs): :calls: `GET /api/v1/courses/:course_id/modules/:module_id/items \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.module.ModuleItem` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.module.ModuleItem` """ return PaginatedList( ModuleItem, diff --git a/pycanvas/page.py b/pycanvas/page.py index 3dc5e77a..bdef923a 100644 --- a/pycanvas/page.py +++ b/pycanvas/page.py @@ -126,7 +126,12 @@ def get_revision_by_id(self, revision_id, **kwargs): """ response = self._requester.request( 'GET', - '%ss/%s/pages/%s/revisions/%s' % (self.parent_type, self.parent_id, self.url, revision_id), + '%ss/%s/pages/%s/revisions/%s' % ( + self.parent_type, + self.parent_id, + self.url, + revision_id + ), **combine_kwargs(**kwargs) ) pagerev_json = response.json() @@ -144,7 +149,8 @@ def list_revisions(self, **kwargs): :calls: `GET /api/v1/courses/:course_id/pages/:url/revisions \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.pagerevision.PageRevision` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.pagerevision.PageRevision` """ return PaginatedList( PageRevision, @@ -168,7 +174,12 @@ def revert_to_revision(self, revision_id): """ response = self._requester.request( 'POST', - '%ss/%s/pages/%s/revisions/%s' % (self.parent_type, self.parent_id, self.url, revision_id), + '%ss/%s/pages/%s/revisions/%s' % ( + self.parent_type, + self.parent_id, + self.url, + revision_id + ), ) pagerev_json = response.json() if self.parent_type == "group": diff --git a/pycanvas/section.py b/pycanvas/section.py index 2cc25720..3f274f79 100644 --- a/pycanvas/section.py +++ b/pycanvas/section.py @@ -19,7 +19,8 @@ def get_enrollments(self, **kwargs): :calls: `GET /api/v1/sections/:section_id/enrollments \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.enrollment.Enrollment` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.enrollment.Enrollment` """ from enrollment import Enrollment diff --git a/pycanvas/user.py b/pycanvas/user.py index d46c3027..8d3a7bb2 100644 --- a/pycanvas/user.py +++ b/pycanvas/user.py @@ -219,7 +219,8 @@ def get_assignments(self, course_id, **kwargs): :calls: `GET /api/v1/users/:user_id/courses/:course_id/assignments \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.assignment.Assignment` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.assignment.Assignment` """ from assignment import Assignment @@ -238,7 +239,8 @@ def get_enrollments(self, **kwargs): :calls: `GET /api/v1/users/:user_id/enrollments \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.enrollment.Enrollment` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.enrollment.Enrollment` """ from enrollment import Enrollment From 7fb850433c307b21bb92238b761488cc0a806448 Mon Sep 17 00:00:00 2001 From: Jesse McBride Date: Tue, 22 Nov 2016 14:27:35 -0500 Subject: [PATCH 106/145] Modify docstring. --- pycanvas/conversation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycanvas/conversation.py b/pycanvas/conversation.py index b894d50d..9eecfe97 100644 --- a/pycanvas/conversation.py +++ b/pycanvas/conversation.py @@ -92,9 +92,9 @@ def add_message(self, body, **kwargs): def delete_messages(self, remove): """ Delete messages from this conversation. + Note that this only affects this user's view of the conversation. - If all messages are deleted, the conversation will be as well - (equivalent to DELETE) by canvas + If all messages are deleted, the conversation will be as well. :calls: `POST /api/v1/conversations/:id/remove_messages \ `_ From 319bd04329a48e472c06c4c3cab51260debf4819 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Tue, 22 Nov 2016 15:34:10 -0500 Subject: [PATCH 107/145] Implemented InvalidAccessToken. Renamed PermissionError to Unauthorized. Updated tests. --- pycanvas/exceptions.py | 2 +- pycanvas/requester.py | 10 ++++++++-- tests/fixtures/requests.json | 13 +++++++++++-- tests/test_requester.py | 19 +++++++++++++------ 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/pycanvas/exceptions.py b/pycanvas/exceptions.py index 626dae13..42d18c0d 100644 --- a/pycanvas/exceptions.py +++ b/pycanvas/exceptions.py @@ -33,7 +33,7 @@ class InvalidAccessToken(CanvasException): pass -class PermissionError(CanvasException): +class Unauthorized(CanvasException): """PyCanvas's key is valid, but is unauthorized to access the requested resource.""" pass diff --git a/pycanvas/requester.py b/pycanvas/requester.py index 944d8225..6a45305a 100644 --- a/pycanvas/requester.py +++ b/pycanvas/requester.py @@ -1,4 +1,7 @@ -from pycanvas.exceptions import BadRequest, CanvasException, PermissionError, ResourceDoesNotExist +from pycanvas.exceptions import ( + BadRequest, CanvasException, InvalidAccessToken, ResourceDoesNotExist, + Unauthorized +) import requests @@ -64,7 +67,10 @@ def request(self, method, endpoint=None, headers=None, use_auth=True, url=None, if response.status_code == 400: raise BadRequest(response.json()) elif response.status_code == 401: - raise PermissionError(response.json()) + if 'WWW-Authenticate' in response.headers: + raise InvalidAccessToken(response.json()) + else: + raise Unauthorized(response.json()) elif response.status_code == 404: raise ResourceDoesNotExist('Not Found') elif response.status_code == 500: diff --git a/tests/fixtures/requests.json b/tests/fixtures/requests.json index 2c9354ed..12a04ce6 100644 --- a/tests/fixtures/requests.json +++ b/tests/fixtures/requests.json @@ -5,9 +5,18 @@ "data": {}, "status_code": 400 }, - "401": { + "401_invalid_access_token": { "method": "ANY", - "endpoint": "401", + "endpoint": "401_invalid_access_token", + "data": {}, + "headers": { + "WWW-Authenticate": "Bearer realm=\"canvas-lms\"" + }, + "status_code": 401 + }, + "401_unauthorized": { + "method": "ANY", + "endpoint": "401_unauthorized", "data": {}, "status_code": 401 }, diff --git a/tests/test_requester.py b/tests/test_requester.py index 2a40a995..47b15bff 100644 --- a/tests/test_requester.py +++ b/tests/test_requester.py @@ -4,7 +4,10 @@ import settings from pycanvas import Canvas -from pycanvas.exceptions import BadRequest, CanvasException, PermissionError, ResourceDoesNotExist +from pycanvas.exceptions import ( + BadRequest, CanvasException, InvalidAccessToken, ResourceDoesNotExist, + Unauthorized +) from util import register_uris @@ -16,8 +19,8 @@ class TestRequester(unittest.TestCase): def setUpClass(self): requires = { 'requests': [ - '400', '401', '404', '500', - 'delete', 'get', 'post', 'put' + '400', '401_invalid_access_token', '401_unauthorized', '404', + '500', 'delete', 'get', 'post', 'put' ] } @@ -47,9 +50,13 @@ def test_request_400(self): with self.assertRaises(BadRequest): self.requester.request('GET', '400') - def test_request_401(self): - with self.assertRaises(PermissionError): - self.requester.request('GET', '401') + def test_request_401_InvalidAccessToken(self): + with self.assertRaises(InvalidAccessToken): + self.requester.request('GET', '401_invalid_access_token') + + def test_request_401_Unauthorized(self): + with self.assertRaises(Unauthorized): + self.requester.request('GET', '401_unauthorized') def test_request_404(self): with self.assertRaises(ResourceDoesNotExist): From 5081d7739d14ebbfbc9058afdd61933511e6f812 Mon Sep 17 00:00:00 2001 From: Jesse McBride Date: Tue, 22 Nov 2016 15:56:49 -0500 Subject: [PATCH 108/145] Fix import structure --- pycanvas/requester.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pycanvas/requester.py b/pycanvas/requester.py index 6a45305a..16889c58 100644 --- a/pycanvas/requester.py +++ b/pycanvas/requester.py @@ -1,8 +1,9 @@ +import requests + from pycanvas.exceptions import ( BadRequest, CanvasException, InvalidAccessToken, ResourceDoesNotExist, Unauthorized ) -import requests class Requester(object): From f3ad581ef3437e5eb2bed8a862c483d0b999b08c Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Wed, 23 Nov 2016 12:59:49 -0500 Subject: [PATCH 109/145] Standardized format of __str__ methods. Updated to use format method instead of (deprecated) % notation. Corrected Process to Progress --- docs/class-reference.rst | 2 +- docs/process-ref.rst | 4 ++-- pycanvas/account.py | 17 ++++------------- pycanvas/assignment.py | 6 +----- pycanvas/avatar.py | 6 +----- pycanvas/canvas.py | 8 ++++---- pycanvas/conversation.py | 2 +- pycanvas/course.py | 8 ++------ pycanvas/enrollment.py | 7 +------ pycanvas/external_tool.py | 2 +- pycanvas/group.py | 2 +- pycanvas/module.py | 11 ++--------- pycanvas/page.py | 10 ++-------- pycanvas/page_view.py | 2 +- pycanvas/process.py | 7 ------- pycanvas/progress.py | 7 +++++++ pycanvas/quiz.py | 5 +---- pycanvas/section.py | 6 +++--- pycanvas/user.py | 5 +++-- tests/fixtures/user.json | 12 ++++++++---- tests/test_canvas.py | 19 ++++++++++++++----- 21 files changed, 60 insertions(+), 88 deletions(-) delete mode 100644 pycanvas/process.py create mode 100644 pycanvas/progress.py diff --git a/docs/class-reference.rst b/docs/class-reference.rst index ac6b8c4a..830cdd3b 100644 --- a/docs/class-reference.rst +++ b/docs/class-reference.rst @@ -12,7 +12,7 @@ Class Reference group-ref module-ref page-ref - process-ref + progress-ref quiz-ref section-ref user-ref diff --git a/docs/process-ref.rst b/docs/process-ref.rst index 9ffd1e6f..c8a4ba6a 100644 --- a/docs/process-ref.rst +++ b/docs/process-ref.rst @@ -1,6 +1,6 @@ ============ -Process +Progress ============ -.. autoclass:: pycanvas.process.Process +.. autoclass:: pycanvas.progress.Progress :members: diff --git a/pycanvas/account.py b/pycanvas/account.py index 49a2ce2b..aae28c23 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -7,10 +7,7 @@ class Account(CanvasObject): def __str__(self): - return "id: %s, name: %s" % ( - self.id, - self.name - ) + return "{} ({})".format(self.name, self.id) def close_notification_for_user(self, user, notification): """ @@ -489,21 +486,15 @@ def get_enrollment(self, enrollment_id, **kwargs): class AccountNotification(CanvasObject): def __str__(self): # pragma: no cover - return "subject: %s, message: %s" % ( - self.subject, - self.message - ) + return str(self.subject) class AccountReport(CanvasObject): def __str__(self): # pragma: no cover - return "id: %s, report: %s" % ( - self.id, - self.report - ) + return "{} ({})".format(self.report, self.id) class Role(CanvasObject): def __str__(self): # pragma: no cover - return "id: %s" % (self.id) + return "{} ({})".format(self.label, self.base_role_type) diff --git a/pycanvas/assignment.py b/pycanvas/assignment.py index 2c5af1e8..082791ca 100644 --- a/pycanvas/assignment.py +++ b/pycanvas/assignment.py @@ -5,11 +5,7 @@ class Assignment(CanvasObject): def __str__(self): # pragma: no cover - return "id: %s, name: %s, description: %s" % ( - self.id, - self.name, - self.description - ) + return "{} ({})".format(self.name, self.id) def delete(self): """ diff --git a/pycanvas/avatar.py b/pycanvas/avatar.py index 4e10a23c..8c88471c 100644 --- a/pycanvas/avatar.py +++ b/pycanvas/avatar.py @@ -4,8 +4,4 @@ class Avatar(CanvasObject): def __str__(self): # pragma: no cover - return "type: %s, display_name: %s, url: %s" % ( - self.type, - self.display_name, - self.url, - ) + return str(self.display_name) diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index 6691457c..ed54fbcb 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -464,10 +464,10 @@ def conversations_batch_update(self, conversation_ids, event): :type conversation_ids: `list` of `str` :param event: The action to take on each conversation. :type event: `str` - :rtype: :class:`pycanvas.process.Process` + :rtype: :class:`pycanvas.progress.Progress` """ - from pycanvas.process import Process + from pycanvas.progress import Progress ALLOWED_EVENTS = [ 'mark_as_read', @@ -500,8 +500,8 @@ def conversations_batch_update(self, conversation_ids, event): event=event, **{"conversation_ids[]": conversation_ids} ) - return_process = Process(self.__requester, response.json()) - return return_process + return_progress = Progress(self.__requester, response.json()) + return return_progress except ValueError as e: return e diff --git a/pycanvas/conversation.py b/pycanvas/conversation.py index 9eecfe97..c0cd0ef8 100644 --- a/pycanvas/conversation.py +++ b/pycanvas/conversation.py @@ -5,7 +5,7 @@ class Conversation(CanvasObject): def __str__(self): - return "%s %s" % (self.id, self.subject) + return "{} ({})".format(self.subject, self.id) def edit(self, **kwargs): """ diff --git a/pycanvas/course.py b/pycanvas/course.py index a5c16391..0e131341 100644 --- a/pycanvas/course.py +++ b/pycanvas/course.py @@ -9,7 +9,7 @@ class Course(CanvasObject): def __str__(self): - return "%s %s %s" % (self.id, self.course_code, self.name) + return "{} {} ({})".format(self.course_code, self.name, self.id) def conclude(self): """ @@ -677,11 +677,7 @@ def create_course_section(self, **kwargs): class CourseNickname(CanvasObject): def __str__(self): - return "course_id: %s, name: %s, nickname: %s, " % ( - self.course_id, - self.name, - self.nickname - ) + return "{} ({})".format(self.nickname, self.course_id) def remove(self): """ diff --git a/pycanvas/enrollment.py b/pycanvas/enrollment.py index 978abbc1..08080900 100644 --- a/pycanvas/enrollment.py +++ b/pycanvas/enrollment.py @@ -4,12 +4,7 @@ class Enrollment(CanvasObject): def __str__(self): - return "id: %s, course_id: %s, user_id: %s, type: %s, " % ( - self.id, - self.course_id, - self.user_id, - self.type - ) + return "{} ({})".format(self.type, self.id) def deactivate(self, task): """ diff --git a/pycanvas/external_tool.py b/pycanvas/external_tool.py index c4e599aa..175b5be5 100644 --- a/pycanvas/external_tool.py +++ b/pycanvas/external_tool.py @@ -6,7 +6,7 @@ class ExternalTool(CanvasObject): def __str__(self): - return "%s %s %s" % (self.id, self.name, self.description) + return "{} ({})".format(self.name. self.id) @property def parent_id(self): diff --git a/pycanvas/group.py b/pycanvas/group.py index e5f429f1..83bd6d64 100644 --- a/pycanvas/group.py +++ b/pycanvas/group.py @@ -7,7 +7,7 @@ class Group(CanvasObject): def __str__(self): - return "%s %s %s" % (self.id, self.name, self.description) + return "{} ({})".format(self.name, self.id) def show_front_page(self): """ diff --git a/pycanvas/module.py b/pycanvas/module.py index 07afbc23..83429a41 100644 --- a/pycanvas/module.py +++ b/pycanvas/module.py @@ -7,10 +7,7 @@ class Module(CanvasObject): def __str__(self): - return "id: %s, name: %s" % ( - self.id, - self.name, - ) + return "{} ({})".format(self.name, self.id) def edit(self, **kwargs): """ @@ -143,11 +140,7 @@ def create_module_item(self, module_item, **kwargs): class ModuleItem(CanvasObject): def __str__(self): - return "id: %s, title: %s, description: %s" % ( - self.id, - self.title, - self.module_id - ) + return "{} ({})".format(self.title, self.id) def edit(self, **kwargs): """ diff --git a/pycanvas/page.py b/pycanvas/page.py index bdef923a..ff5982ab 100644 --- a/pycanvas/page.py +++ b/pycanvas/page.py @@ -7,10 +7,7 @@ class Page(CanvasObject): def __str__(self): - return "url: %s, title: %s" % ( - self.url, - self.title - ) + return "{} ({})".format(self.title, self.url) def edit(self, **kwargs): """ @@ -193,10 +190,7 @@ def revert_to_revision(self, revision_id): class PageRevision(CanvasObject): def __str__(self): - return "revision_id: %s, updated_at: %s" % ( - self.id, - self.updated_at - ) + return "{} ({})".format(self.updated_at, self.revision_id) @property def parent_id(self): diff --git a/pycanvas/page_view.py b/pycanvas/page_view.py index 2b4e49e8..3f11321d 100644 --- a/pycanvas/page_view.py +++ b/pycanvas/page_view.py @@ -4,4 +4,4 @@ class PageView(CanvasObject): def __str__(self): - return "%s %s %s" % (self.id, self.url, self.created_at) + return "{} ({})".format(self.context_type, self.id) diff --git a/pycanvas/process.py b/pycanvas/process.py deleted file mode 100644 index bb876ebf..00000000 --- a/pycanvas/process.py +++ /dev/null @@ -1,7 +0,0 @@ -from pycanvas.canvas_object import CanvasObject - - -class Process(CanvasObject): - - def __str__(self): # pragma: no cover - return "%s: %s, %s" % (self.id, self.tag, self.workflow_state) diff --git a/pycanvas/progress.py b/pycanvas/progress.py new file mode 100644 index 00000000..8a3212b7 --- /dev/null +++ b/pycanvas/progress.py @@ -0,0 +1,7 @@ +from pycanvas.canvas_object import CanvasObject + + +class Progress(CanvasObject): + + def __str__(self): # pragma: no cover + return "{} - {} ({})".format(self.tag, self.workflow_state, self.id) diff --git a/pycanvas/quiz.py b/pycanvas/quiz.py index 545cba79..f31d1d71 100644 --- a/pycanvas/quiz.py +++ b/pycanvas/quiz.py @@ -5,10 +5,7 @@ class Quiz(CanvasObject): def __str__(self): - return "id %s, title: %s" % ( - self.id, - self.title - ) + return "{} ({})".format(self.title, self.id) def edit(self, **kwargs): """ diff --git a/pycanvas/section.py b/pycanvas/section.py index 3f274f79..5316652f 100644 --- a/pycanvas/section.py +++ b/pycanvas/section.py @@ -6,10 +6,10 @@ class Section(CanvasObject): def __str__(self): - return 'Section #%s \"%s\" |course_id: %s' % ( - self.id, + return '{} - {} ({})'.format( self.name, - self.course_id + self.course_id, + self.id, ) def get_enrollments(self, **kwargs): diff --git a/pycanvas/user.py b/pycanvas/user.py index 8d3a7bb2..c38773d6 100644 --- a/pycanvas/user.py +++ b/pycanvas/user.py @@ -7,7 +7,7 @@ class User(CanvasObject): def __str__(self): - return "%s" % (self.name) + return "{} ({})".format(self.name, self.id) def get_profile(self, **kwargs): """ @@ -70,7 +70,8 @@ def get_missing_submissions(self): :calls: `GET /api/v1/users/:user_id/missing_submissions \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.assignment.Assignment` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.assignment.Assignment` """ from assignment import Assignment diff --git a/tests/fixtures/user.json b/tests/fixtures/user.json index 1754dafa..5322d1c9 100644 --- a/tests/fixtures/user.json +++ b/tests/fixtures/user.json @@ -340,12 +340,14 @@ { "id": "123fb561-c9ce-7ed9-8e42-31e0aab47e18", "url": "mock://example.com/test", - "created_at": "2013-01-01T12:00:00Z" + "created_at": "2013-01-01T12:00:00Z", + "context_type": "Course" }, { "id": "4c7f44f9-41b9-ff59-03ad-3215cb246966", "url": "mock://example.com/hello", - "created_at": "2014-01-01T12:00:00Z" + "created_at": "2014-01-01T12:00:00Z", + "context_type": "User" } ], "headers": { @@ -360,12 +362,14 @@ { "id": "77f111c9-5b0b-46cb-92a2-34108ebe7b5b", "url": "mock://example.com/login", - "created_at": "2015-01-01T12:00:00Z" + "created_at": "2015-01-01T12:00:00Z", + "context_type": null }, { "id": "69ec8b6f-e123-2af8-dafb-561fdc711f6b", "url": "mock://example.com/logout", - "created_at": "2016-01-01T12:00:00Z" + "created_at": "2016-01-01T12:00:00Z", + "context_type": "UserProfile" } ], "status_code": 200 diff --git a/tests/test_canvas.py b/tests/test_canvas.py index a7eb9c8f..541ffaf2 100644 --- a/tests/test_canvas.py +++ b/tests/test_canvas.py @@ -253,20 +253,29 @@ def test_conversations_get_running_batches(self): # batch_update() def test_conversations_batch_update(self): - from pycanvas.process import Process + from pycanvas.progress import Progress conversation_ids = [1, 2] this_event = "mark_as_read" - result = self.canvas.conversations_batch_update(event=this_event, conversation_ids=conversation_ids) - assert isinstance(result, Process) + result = self.canvas.conversations_batch_update( + event=this_event, + conversation_ids=conversation_ids + ) + assert isinstance(result, Progress) def test_conversations_batch_updated_fail_on_event(self): conversation_ids = [1, 2] this_event = "this doesn't work" - result = self.canvas.conversations_batch_update(event=this_event, conversation_ids=conversation_ids) + result = self.canvas.conversations_batch_update( + event=this_event, + conversation_ids=conversation_ids + ) assert isinstance(result, ValueError) def test_conversations_batch_updated_fail_on_ids(self): conversation_ids = [None] * 501 this_event = "mark_as_read" - result = self.canvas.conversations_batch_update(event=this_event, conversation_ids=conversation_ids) + result = self.canvas.conversations_batch_update( + event=this_event, + conversation_ids=conversation_ids + ) assert isinstance(result, ValueError) From 849a4b095d3ae1194f34a864aacaf5811f54ea7c Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Wed, 23 Nov 2016 13:01:11 -0500 Subject: [PATCH 110/145] fixed typo --- pycanvas/external_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycanvas/external_tool.py b/pycanvas/external_tool.py index 175b5be5..de1b1579 100644 --- a/pycanvas/external_tool.py +++ b/pycanvas/external_tool.py @@ -6,7 +6,7 @@ class ExternalTool(CanvasObject): def __str__(self): - return "{} ({})".format(self.name. self.id) + return "{} ({})".format(self.name, self.id) @property def parent_id(self): From 4484a89204cbb68fc55473217fb541a1b8146df6 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Mon, 28 Nov 2016 10:42:55 -0500 Subject: [PATCH 111/145] Use specific imports --- pycanvas/account.py | 28 ++++++++++---------- pycanvas/assignment.py | 4 +-- pycanvas/avatar.py | 2 +- pycanvas/course.py | 50 ++++++++++++++++++------------------ pycanvas/enrollment.py | 2 +- pycanvas/external_tool.py | 10 ++++---- pycanvas/group.py | 18 ++++++------- pycanvas/module.py | 8 +++--- pycanvas/page.py | 15 +++++------ pycanvas/page_view.py | 2 +- pycanvas/quiz.py | 4 +-- pycanvas/section.py | 8 +++--- pycanvas/upload.py | 2 +- pycanvas/user.py | 20 +++++++-------- tests/test_account.py | 4 +-- tests/test_assignment.py | 2 +- tests/test_course.py | 6 ++--- tests/test_enrollment.py | 4 +-- tests/test_external_tool.py | 4 +-- tests/test_group.py | 4 +-- tests/test_module.py | 21 +++++++-------- tests/test_page.py | 8 +++--- tests/test_page_view.py | 2 +- tests/test_paginated_list.py | 4 +-- tests/test_quiz.py | 3 ++- tests/test_requester.py | 4 +-- tests/test_section.py | 4 +-- tests/test_uploader.py | 4 +-- tests/test_user.py | 4 +-- tests/test_util.py | 4 +-- 30 files changed, 128 insertions(+), 127 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index 49a2ce2b..5c4e21f6 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -1,7 +1,7 @@ -from canvas_object import CanvasObject -from exceptions import RequiredFieldMissing -from paginated_list import PaginatedList -from util import combine_kwargs, obj_or_id +from pycanvas.canvas_object import CanvasObject +from pycanvas.exceptions import RequiredFieldMissing +from pycanvas.paginated_list import PaginatedList +from pycanvas.util import combine_kwargs, obj_or_id class Account(CanvasObject): @@ -26,7 +26,7 @@ def close_notification_for_user(self, user, notification): :type notification: :class:`pycanvas.account.AccountNotification` or int :rtype: :class:`pycanvas.account.AccountNotification` """ - from user import User + from pycanvas.user import User user_id = obj_or_id(user, "user", (User,)) notif_id = obj_or_id(notification, "notif", (AccountNotification,)) @@ -62,7 +62,7 @@ def create_course(self, **kwargs): :rtype: :class:`pycanvas.course.Course` """ - from course import Course + from pycanvas.course import Course response = self._requester.request( 'POST', 'accounts/%s/courses' % (self.id), @@ -103,7 +103,7 @@ def create_user(self, pseudonym, **kwargs): :type pseudonym: dict :rtype: :class:`pycanvas.user.User` """ - from user import User + from pycanvas.user import User if isinstance(pseudonym, dict) and 'unique_id' in pseudonym: kwargs['pseudonym'] = pseudonym @@ -165,7 +165,7 @@ def delete_user(self, user): :type user: :class:`pycanvas.user.User` or int :rtype: :class:`pycanvas.user.User` """ - from user import User + from pycanvas.user import User user_id = obj_or_id(user, "user", (User,)) @@ -184,7 +184,7 @@ def get_courses(self, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.course.Course` """ - from course import Course + from pycanvas.course import Course return PaginatedList( Course, @@ -201,7 +201,7 @@ def get_external_tool(self, tool_id): :rtype: :class:`pycanvas.external_tool.ExternalTool` """ - from external_tool import ExternalTool + from pycanvas.external_tool import ExternalTool response = self._requester.request( 'GET', @@ -220,7 +220,7 @@ def get_external_tools(self, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.external_tool.ExternalTool` """ - from external_tool import ExternalTool + from pycanvas.external_tool import ExternalTool return PaginatedList( ExternalTool, @@ -296,7 +296,7 @@ def get_users(self, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.user.User` """ - from user import User + from pycanvas.user import User return PaginatedList( User, @@ -320,7 +320,7 @@ def get_user_notifications(self, user): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.account.AccountNotification` """ - from user import User + from pycanvas.user import User user_id = obj_or_id(user, "user", (User,)) @@ -477,7 +477,7 @@ def get_enrollment(self, enrollment_id, **kwargs): :type enrollment_id: int :rtype: :class:`pycanvas.enrollment.Enrollment` """ - from enrollment import Enrollment + from pycanvas.enrollment import Enrollment response = self._requester.request( 'GET', diff --git a/pycanvas/assignment.py b/pycanvas/assignment.py index 2c5af1e8..61884579 100644 --- a/pycanvas/assignment.py +++ b/pycanvas/assignment.py @@ -1,5 +1,5 @@ -from canvas_object import CanvasObject -from util import combine_kwargs +from pycanvas.canvas_object import CanvasObject +from pycanvas.util import combine_kwargs class Assignment(CanvasObject): diff --git a/pycanvas/avatar.py b/pycanvas/avatar.py index 4e10a23c..2268aca9 100644 --- a/pycanvas/avatar.py +++ b/pycanvas/avatar.py @@ -1,4 +1,4 @@ -from canvas_object import CanvasObject +from pycanvas.canvas_object import CanvasObject class Avatar(CanvasObject): diff --git a/pycanvas/course.py b/pycanvas/course.py index a5c16391..3d98bc76 100644 --- a/pycanvas/course.py +++ b/pycanvas/course.py @@ -1,9 +1,9 @@ -from canvas_object import CanvasObject -from exceptions import RequiredFieldMissing -from upload import Uploader -from util import combine_kwargs -from page import Page -from paginated_list import PaginatedList +from pycanvas.canvas_object import CanvasObject +from pycanvas.exceptions import RequiredFieldMissing +from pycanvas.upload import Uploader +from pycanvas.util import combine_kwargs +from pycanvas.page import Page +from pycanvas.paginated_list import PaginatedList class Course(CanvasObject): @@ -82,7 +82,7 @@ def get_user(self, user_id, user_id_type=None): :type user_id_type: str :rtype: :class:`pycanvas.user.User` """ - from user import User + from pycanvas.user import User if user_id_type: uri = 'courses/%s/users/%s:%s' % (self.id, user_id_type, user_id) @@ -107,7 +107,7 @@ def get_users(self, search_term=None, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.user.User` """ - from user import User + from pycanvas.user import User return PaginatedList( User, @@ -130,7 +130,7 @@ def enroll_user(self, user, enrollment_type, **kwargs): :type enrollment_type: str :rtype: :class:`pycanvas.enrollment.Enrollment` """ - from enrollment import Enrollment + from pycanvas.enrollment import Enrollment kwargs['enrollment[user_id]'] = user.id kwargs['enrollment[type]'] = enrollment_type @@ -153,7 +153,7 @@ def get_recent_students(self): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.user.User` """ - from user import User + from pycanvas.user import User return PaginatedList( User, @@ -259,7 +259,7 @@ def get_enrollments(self, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.enrollment.Enrollment` """ - from enrollment import Enrollment + from pycanvas.enrollment import Enrollment return PaginatedList( Enrollment, self._requester, @@ -279,7 +279,7 @@ def get_assignment(self, assignment_id, **kwargs): :type assignment_id: int :rtype: :class:`pycanvas.assignment.Assignment` """ - from assignment import Assignment + from pycanvas.assignment import Assignment response = self._requester.request( 'GET', @@ -298,7 +298,7 @@ def get_assignments(self, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.assignment.Assignment` """ - from assignment import Assignment + from pycanvas.assignment import Assignment return PaginatedList( Assignment, @@ -321,7 +321,7 @@ def create_assignment(self, assignment, **kwargs): :type assignment: dict :rtype: :class:`pycanvas.assignment.Assignment` """ - from assignment import Assignment + from pycanvas.assignment import Assignment if isinstance(assignment, dict) and 'name' in assignment: kwargs['assignment'] = assignment @@ -345,7 +345,7 @@ def get_quizzes(self, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.quiz.Quiz` """ - from quiz import Quiz + from pycanvas.quiz import Quiz return PaginatedList( Quiz, self._requester, @@ -366,7 +366,7 @@ def get_quiz(self, quiz_id): :type quiz_id: int :rtype: :class:`pycanvas.quiz.Quiz` """ - from quiz import Quiz + from pycanvas.quiz import Quiz response = self._requester.request( 'GET', 'courses/%s/quizzes/%s' % (self.id, quiz_id) @@ -387,7 +387,7 @@ def create_quiz(self, quiz, **kwargs): :type quiz: dict :rtype: :class:`pycanvas.quiz.Quiz` """ - from quiz import Quiz + from pycanvas.quiz import Quiz if isinstance(quiz, dict) and 'title' in quiz: kwargs['quiz'] = quiz @@ -413,7 +413,7 @@ def get_modules(self, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.module.Module` """ - from module import Module + from pycanvas.module import Module return PaginatedList( Module, @@ -435,7 +435,7 @@ def get_module(self, module_id, **kwargs): :type module_id: int :rtype: :class:`pycanvas.module.Module` """ - from module import Module + from pycanvas.module import Module response = self._requester.request( 'GET', @@ -458,7 +458,7 @@ def create_module(self, module, **kwargs): :returns: The created module. :rtype: :class:`pycanvas.module.Module` """ - from module import Module + from pycanvas.module import Module if isinstance(module, dict) and 'name' in module: kwargs['module'] = module @@ -482,7 +482,7 @@ def get_external_tool(self, tool_id): :rtype: :class:`pycanvas.external_tool.ExternalTool` """ - from external_tool import ExternalTool + from pycanvas.external_tool import ExternalTool response = self._requester.request( 'GET', @@ -501,7 +501,7 @@ def get_external_tools(self, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.external_tool.ExternalTool` """ - from external_tool import ExternalTool + from pycanvas.external_tool import ExternalTool return PaginatedList( ExternalTool, @@ -523,7 +523,7 @@ def get_section(self, section_id): :type section_id: int :rtype: :class:`pycanvas.section.Section` """ - from section import Section + from pycanvas.section import Section response = self._requester.request( 'GET', @@ -645,7 +645,7 @@ def list_sections(self, **kwargs): :rtype: :class: `pycanvas.section.Section` """ - from section import Section + from pycanvas.section import Section return PaginatedList( Section, self._requester, @@ -664,7 +664,7 @@ def create_course_section(self, **kwargs): :rtype: :class:`pycanvas.course.Section` """ - from section import Section + from pycanvas.section import Section response = self._requester.request( 'POST', 'courses/%s/sections' % (self.id), diff --git a/pycanvas/enrollment.py b/pycanvas/enrollment.py index 978abbc1..692fe8da 100644 --- a/pycanvas/enrollment.py +++ b/pycanvas/enrollment.py @@ -1,4 +1,4 @@ -from canvas_object import CanvasObject +from pycanvas.canvas_object import CanvasObject class Enrollment(CanvasObject): diff --git a/pycanvas/external_tool.py b/pycanvas/external_tool.py index c4e599aa..4b0f41d2 100644 --- a/pycanvas/external_tool.py +++ b/pycanvas/external_tool.py @@ -1,6 +1,6 @@ -from canvas_object import CanvasObject -from exceptions import CanvasException -from util import combine_kwargs +from pycanvas.canvas_object import CanvasObject +from pycanvas.exceptions import CanvasException +from pycanvas.util import combine_kwargs class ExternalTool(CanvasObject): @@ -42,8 +42,8 @@ def get_parent(self): :rtype: :class:`pycanvas.account.Account` or :class:`pycanvas.account.Course` """ - from account import Account - from course import Course + from pycanvas.account import Account + from pycanvas.course import Course response = self._requester.request( 'GET', diff --git a/pycanvas/group.py b/pycanvas/group.py index e5f429f1..2dc09986 100644 --- a/pycanvas/group.py +++ b/pycanvas/group.py @@ -1,7 +1,7 @@ -from canvas_object import CanvasObject -from paginated_list import PaginatedList -from util import combine_kwargs -from exceptions import RequiredFieldMissing +from pycanvas.canvas_object import CanvasObject +from pycanvas.paginated_list import PaginatedList +from pycanvas.util import combine_kwargs +from pycanvas.exceptions import RequiredFieldMissing class Group(CanvasObject): @@ -18,7 +18,7 @@ def show_front_page(self): :rtype: :class:`pycanvas.group.Group` """ - from course import Page + from pycanvas.course import Page response = self._requester.request( 'GET', @@ -38,7 +38,7 @@ def edit_front_page(self, **kwargs): :rtype: :class:`pycanvas.group.Group` """ - from course import Page + from pycanvas.course import Page response = self._requester.request( 'PUT', @@ -59,7 +59,7 @@ def get_pages(self, **kwargs): :rtype: :class:`pycanvas.groups.Group` """ - from course import Page + from pycanvas.course import Page return PaginatedList( Page, self._requester, @@ -81,7 +81,7 @@ def create_page(self, wiki_page, **kwargs): :returns: The created page. :rtype: :class: `pycanvas.groups.Group` """ - from course import Page + from pycanvas.course import Page if isinstance(wiki_page, dict) and 'title' in wiki_page: kwargs['wiki_page'] = wiki_page @@ -110,7 +110,7 @@ def get_page(self, url): :returns: The specified page. :rtype: :class: `pycanvas.groups.Group` """ - from course import Page + from pycanvas.course import Page response = self._requester.request( 'GET', diff --git a/pycanvas/module.py b/pycanvas/module.py index 07afbc23..61feaf8f 100644 --- a/pycanvas/module.py +++ b/pycanvas/module.py @@ -1,7 +1,7 @@ -from canvas_object import CanvasObject -from exceptions import RequiredFieldMissing -from paginated_list import PaginatedList -from util import combine_kwargs +from pycanvas.canvas_object import CanvasObject +from pycanvas.exceptions import RequiredFieldMissing +from pycanvas.paginated_list import PaginatedList +from pycanvas.util import combine_kwargs class Module(CanvasObject): diff --git a/pycanvas/page.py b/pycanvas/page.py index bdef923a..f58712bd 100644 --- a/pycanvas/page.py +++ b/pycanvas/page.py @@ -1,7 +1,6 @@ -from canvas_object import CanvasObject - -from util import combine_kwargs -from paginated_list import PaginatedList +from pycanvas.canvas_object import CanvasObject +from pycanvas.util import combine_kwargs +from pycanvas.paginated_list import PaginatedList class Page(CanvasObject): @@ -83,8 +82,8 @@ def get_parent(self): :rtype: :class:`pycanvas.group.Group` or :class:`pycanvas.course.Course` """ - from group import Group - from course import Course + from pycanvas.group import Group + from pycanvas.course import Course response = self._requester.request( 'GET', @@ -232,8 +231,8 @@ def get_parent(self): :rtype: :class:`pycanvas.group.Group` or :class:`pycanvas.course.Course` """ - from group import Group - from course import Course + from pycanvas.group import Group + from pycanvas.course import Course response = self._requester.request( 'GET', diff --git a/pycanvas/page_view.py b/pycanvas/page_view.py index 2b4e49e8..3615f79f 100644 --- a/pycanvas/page_view.py +++ b/pycanvas/page_view.py @@ -1,4 +1,4 @@ -from canvas_object import CanvasObject +from pycanvas.canvas_object import CanvasObject class PageView(CanvasObject): diff --git a/pycanvas/quiz.py b/pycanvas/quiz.py index 545cba79..8d7440e0 100644 --- a/pycanvas/quiz.py +++ b/pycanvas/quiz.py @@ -1,5 +1,5 @@ -from canvas_object import CanvasObject -from util import combine_kwargs +from pycanvas.canvas_object import CanvasObject +from pycanvas.util import combine_kwargs class Quiz(CanvasObject): diff --git a/pycanvas/section.py b/pycanvas/section.py index 3f274f79..4a95a781 100644 --- a/pycanvas/section.py +++ b/pycanvas/section.py @@ -1,6 +1,6 @@ -from canvas_object import CanvasObject -from paginated_list import PaginatedList -from util import combine_kwargs +from pycanvas.canvas_object import CanvasObject +from pycanvas.paginated_list import PaginatedList +from pycanvas.util import combine_kwargs class Section(CanvasObject): @@ -22,7 +22,7 @@ def get_enrollments(self, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.enrollment.Enrollment` """ - from enrollment import Enrollment + from pycanvas.enrollment import Enrollment return PaginatedList( Enrollment, diff --git a/pycanvas/upload.py b/pycanvas/upload.py index f191847a..98fb9aa4 100644 --- a/pycanvas/upload.py +++ b/pycanvas/upload.py @@ -1,6 +1,6 @@ import os -from util import combine_kwargs +from pycanvas.util import combine_kwargs class Uploader(object): diff --git a/pycanvas/user.py b/pycanvas/user.py index 8d3a7bb2..ebec6163 100644 --- a/pycanvas/user.py +++ b/pycanvas/user.py @@ -1,7 +1,7 @@ -from canvas_object import CanvasObject -from paginated_list import PaginatedList -from upload import Uploader -from util import combine_kwargs, obj_or_id +from pycanvas.canvas_object import CanvasObject +from pycanvas.paginated_list import PaginatedList +from pycanvas.upload import Uploader +from pycanvas.util import combine_kwargs, obj_or_id class User(CanvasObject): @@ -33,7 +33,7 @@ def get_page_views(self, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.course.PageView` """ - from page_view import PageView + from pycanvas.page_view import PageView return PaginatedList( PageView, @@ -52,7 +52,7 @@ def get_courses(self, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.course.Course` """ - from course import Course + from pycanvas.course import Course return PaginatedList( Course, @@ -72,7 +72,7 @@ def get_missing_submissions(self): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.assignment.Assignment` """ - from assignment import Assignment + from pycanvas.assignment import Assignment return PaginatedList( Assignment, @@ -202,7 +202,7 @@ def get_avatars(self): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.avatar.Avatar` """ - from avatar import Avatar + from pycanvas.avatar import Avatar return PaginatedList( Avatar, @@ -222,7 +222,7 @@ def get_assignments(self, course_id, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.assignment.Assignment` """ - from assignment import Assignment + from pycanvas.assignment import Assignment return PaginatedList( Assignment, @@ -242,7 +242,7 @@ def get_enrollments(self, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.enrollment.Enrollment` """ - from enrollment import Enrollment + from pycanvas.enrollment import Enrollment return PaginatedList( Enrollment, diff --git a/tests/test_account.py b/tests/test_account.py index 701f916f..4b9cfdc5 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -3,7 +3,6 @@ import requests_mock -import settings from pycanvas import Canvas from pycanvas.account import Account, AccountNotification, AccountReport, Role from pycanvas.course import Course @@ -11,7 +10,8 @@ from pycanvas.external_tool import ExternalTool from pycanvas.exceptions import RequiredFieldMissing from pycanvas.user import User -from util import register_uris +from tests import settings +from tests.util import register_uris class TestAccount(unittest.TestCase): diff --git a/tests/test_assignment.py b/tests/test_assignment.py index c6adb71e..f2356747 100644 --- a/tests/test_assignment.py +++ b/tests/test_assignment.py @@ -4,7 +4,7 @@ from pycanvas import Canvas from pycanvas.assignment import Assignment -from util import register_uris +from tests.util import register_uris class TestAssignment(unittest.TestCase): diff --git a/tests/test_course.py b/tests/test_course.py index 8665d1d0..812fc822 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -5,18 +5,18 @@ import requests_mock -import settings -from util import register_uris from pycanvas import Canvas from pycanvas.assignment import Assignment from pycanvas.course import Course, CourseNickname, Page from pycanvas.enrollment import Enrollment -from pycanvas.external_tool import ExternalTool from pycanvas.exceptions import ResourceDoesNotExist, RequiredFieldMissing +from pycanvas.external_tool import ExternalTool from pycanvas.module import Module from pycanvas.quiz import Quiz from pycanvas.section import Section from pycanvas.user import User +from tests import settings +from tests.util import register_uris class TestCourse(unittest.TestCase): diff --git a/tests/test_enrollment.py b/tests/test_enrollment.py index 6247e2df..ab41a560 100644 --- a/tests/test_enrollment.py +++ b/tests/test_enrollment.py @@ -2,10 +2,10 @@ import requests_mock -import settings -from util import register_uris from pycanvas.canvas import Canvas from pycanvas.enrollment import Enrollment +from tests import settings +from tests.util import register_uris class TestEnrollment(unittest.TestCase): diff --git a/tests/test_external_tool.py b/tests/test_external_tool.py index 1bef01a0..7f73c72d 100644 --- a/tests/test_external_tool.py +++ b/tests/test_external_tool.py @@ -2,13 +2,13 @@ import requests_mock -import settings -from util import register_uris from pycanvas import Canvas from pycanvas.account import Account from pycanvas.course import Course from pycanvas.exceptions import CanvasException from pycanvas.external_tool import ExternalTool +from tests import settings +from tests.util import register_uris class TestExternalTool(unittest.TestCase): diff --git a/tests/test_group.py b/tests/test_group.py index 0a1fee7a..c3621372 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -2,11 +2,11 @@ import requests_mock -import settings -from util import register_uris from pycanvas import Canvas from pycanvas.course import Page from pycanvas.exceptions import RequiredFieldMissing +from tests import settings +from tests.util import register_uris class TestGroup(unittest.TestCase): diff --git a/tests/test_module.py b/tests/test_module.py index 6d4af0ad..493238bc 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -2,10 +2,11 @@ import settings import requests_mock + from pycanvas import Canvas from pycanvas.exceptions import RequiredFieldMissing from pycanvas.module import Module, ModuleItem -from util import register_uris +from tests.util import register_uris class TestModule(unittest.TestCase): @@ -48,7 +49,7 @@ def test_delete_module(self): assert hasattr(deleted_module, 'course_id') assert deleted_module.course_id == self.course.id - #relock() + # relock() def test_relock(self): relocked_module = self.module.relock() @@ -56,7 +57,7 @@ def test_relock(self): assert hasattr(relocked_module, 'course_id') assert relocked_module.course_id == self.course.id - #list_module_items() + # list_module_items() def test_list_module_items(self): module_items = self.module.list_module_items() module_item_list = [module_item for module_item in module_items] @@ -66,7 +67,7 @@ def test_list_module_items(self): assert hasattr(module_item_list[0], 'course_id') assert module_item_list[0].course_id == self.course.id - #get_module_item() + # get_module_item() def test_get_module_item(self): module_item = self.module.get_module_item(1) @@ -74,7 +75,7 @@ def test_get_module_item(self): assert hasattr(module_item, 'course_id') assert module_item.course_id == self.course.id - #create_module_item() + # create_module_item() def test_create_module_item(self): module_item = self.module.create_module_item( module_item={ @@ -98,7 +99,7 @@ def test_create_module_item_fail2(self): module_item={'type': 'Page'} ) - #__str__ + # __str__ def test__str__(self): string = str(self.module) assert isinstance(string, str) @@ -127,7 +128,7 @@ def setUpClass(self): self.module = self.course.get_module(1) self.module_item = self.module.get_module_item(1) - #edit() + # edit() def test_edit_module_item(self): title = 'New Title' edited_module_item = self.module_item.edit( @@ -140,7 +141,7 @@ def test_edit_module_item(self): assert hasattr(edited_module_item, 'course_id') assert edited_module_item.course_id == self.course.id - #delete() + # delete() def test_delete(self): deleted_module_item = self.module_item.delete() @@ -148,7 +149,7 @@ def test_delete(self): assert hasattr(deleted_module_item, 'course_id') assert deleted_module_item.course_id == self.course.id - #complete(course_id, True) + # complete(course_id, True) def test_complete(self): completed_module_item = self.module_item.complete() @@ -157,7 +158,7 @@ def test_complete(self): assert hasattr(completed_module_item, 'course_id') assert completed_module_item.course_id == self.course.id - #complete(course_id, False) + # complete(course_id, False) def test_uncomplete(self): completed_module_item = self.module_item.uncomplete() diff --git a/tests/test_page.py b/tests/test_page.py index fc3a6f68..1e167494 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -2,12 +2,12 @@ import requests_mock -import settings -from util import register_uris from pycanvas.canvas import Canvas from pycanvas.course import Course from pycanvas.group import Group from pycanvas.page import Page, PageRevision +from tests import settings +from tests.util import register_uris class TestPage(unittest.TestCase): @@ -37,7 +37,7 @@ def setUpClass(self): self.page_course = self.course.get_page('my-url') self.page_group = self.group.get_page('my-url') - #__str__() + # __str__() def test__str__(self): string = str(self.page_course) assert isinstance(string, str) @@ -50,7 +50,7 @@ def test_edit(self): assert hasattr(self.page_course, 'title') assert self.page_course.title == new_title - #reset for future tests + # reset for future tests self.page_course = self.course.get_page('my-url') def test_delete(self): diff --git a/tests/test_page_view.py b/tests/test_page_view.py index 3ea2688d..49c23fba 100644 --- a/tests/test_page_view.py +++ b/tests/test_page_view.py @@ -2,8 +2,8 @@ import requests_mock -import settings from pycanvas import Canvas +from tests import settings from util import register_uris diff --git a/tests/test_paginated_list.py b/tests/test_paginated_list.py index c1895cb1..d11506c2 100644 --- a/tests/test_paginated_list.py +++ b/tests/test_paginated_list.py @@ -2,11 +2,11 @@ import requests_mock -import settings from pycanvas import Canvas from pycanvas.paginated_list import PaginatedList from pycanvas.user import User -from util import register_uris +from tests import settings +from tests.util import register_uris class TestPaginatedList(unittest.TestCase): diff --git a/tests/test_quiz.py b/tests/test_quiz.py index 6fd447c0..c0d21253 100644 --- a/tests/test_quiz.py +++ b/tests/test_quiz.py @@ -2,9 +2,10 @@ import settings import requests_mock + from pycanvas import Canvas from pycanvas.quiz import Quiz -from util import register_uris +from tests.util import register_uris class TestQuiz(unittest.TestCase): diff --git a/tests/test_requester.py b/tests/test_requester.py index 47b15bff..3b6f6b7a 100644 --- a/tests/test_requester.py +++ b/tests/test_requester.py @@ -2,13 +2,13 @@ import requests_mock -import settings from pycanvas import Canvas from pycanvas.exceptions import ( BadRequest, CanvasException, InvalidAccessToken, ResourceDoesNotExist, Unauthorized ) -from util import register_uris +from tests import settings +from tests.util import register_uris class TestRequester(unittest.TestCase): diff --git a/tests/test_section.py b/tests/test_section.py index 30742045..b60b1c3e 100644 --- a/tests/test_section.py +++ b/tests/test_section.py @@ -2,10 +2,10 @@ import settings import requests_mock -from util import register_uris -from pycanvas.enrollment import Enrollment from pycanvas import Canvas +from pycanvas.enrollment import Enrollment from pycanvas.section import Section +from tests.util import register_uris class TestSection(unittest.TestCase): diff --git a/tests/test_uploader.py b/tests/test_uploader.py index f7d38037..5101996d 100644 --- a/tests/test_uploader.py +++ b/tests/test_uploader.py @@ -6,8 +6,8 @@ from pycanvas.canvas import Canvas from pycanvas.upload import Uploader -import settings -from util import register_uris +from tests import settings +from tests.util import register_uris class TestUploader(unittest.TestCase): diff --git a/tests/test_user.py b/tests/test_user.py index 178d64a5..74f4d01e 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -4,8 +4,6 @@ import requests_mock -import settings -from util import register_uris from pycanvas import Canvas from pycanvas.assignment import Assignment from pycanvas.avatar import Avatar @@ -13,6 +11,8 @@ from pycanvas.enrollment import Enrollment from pycanvas.page_view import PageView from pycanvas.user import User +from tests import settings +from tests.util import register_uris class TestUser(unittest.TestCase): diff --git a/tests/test_util.py b/tests/test_util.py index e62d35bb..3ef7e5b0 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -2,12 +2,12 @@ import requests_mock -import settings -from util import register_uris from pycanvas import Canvas from pycanvas.course import CourseNickname from pycanvas.user import User from pycanvas.util import combine_kwargs, obj_or_id +from tests import settings +from tests.util import register_uris class TestCourse(unittest.TestCase): From 8a7fd251454026baf3cf7a0a1aa0300a0f3772bc Mon Sep 17 00:00:00 2001 From: Jesse McBride Date: Tue, 29 Nov 2016 13:03:42 -0500 Subject: [PATCH 112/145] Remove no-cover from __str__ method --- pycanvas/assignment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycanvas/assignment.py b/pycanvas/assignment.py index 082791ca..5d08f473 100644 --- a/pycanvas/assignment.py +++ b/pycanvas/assignment.py @@ -4,7 +4,7 @@ class Assignment(CanvasObject): - def __str__(self): # pragma: no cover + def __str__(self): return "{} ({})".format(self.name, self.id) def delete(self): From 05a278c4c7f5156ede0c3aa4b3576817f78002ce Mon Sep 17 00:00:00 2001 From: Jesse McBride Date: Tue, 29 Nov 2016 15:02:31 -0500 Subject: [PATCH 113/145] Modify import order to be alphabetical, remove unnecessary file docstrings, formatting fixes. --- pycanvas/canvas.py | 2 +- pycanvas/course.py | 4 ++-- pycanvas/exceptions.py | 5 ----- pycanvas/group.py | 2 +- pycanvas/util.py | 8 ++------ 5 files changed, 6 insertions(+), 15 deletions(-) diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index 6691457c..dd892c9e 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -1,9 +1,9 @@ from pycanvas.account import Account from pycanvas.course import Course +from pycanvas.group import Group from pycanvas.paginated_list import PaginatedList from pycanvas.requester import Requester from pycanvas.user import User -from pycanvas.group import Group from pycanvas.util import combine_kwargs diff --git a/pycanvas/course.py b/pycanvas/course.py index 3d98bc76..424f5195 100644 --- a/pycanvas/course.py +++ b/pycanvas/course.py @@ -1,9 +1,9 @@ from pycanvas.canvas_object import CanvasObject from pycanvas.exceptions import RequiredFieldMissing -from pycanvas.upload import Uploader -from pycanvas.util import combine_kwargs from pycanvas.page import Page from pycanvas.paginated_list import PaginatedList +from pycanvas.upload import Uploader +from pycanvas.util import combine_kwargs class Course(CanvasObject): diff --git a/pycanvas/exceptions.py b/pycanvas/exceptions.py index 42d18c0d..859171ac 100644 --- a/pycanvas/exceptions.py +++ b/pycanvas/exceptions.py @@ -1,8 +1,3 @@ -""" -A collection of PyCanvas exception classes. -""" - - class CanvasException(Exception): # pragma: no cover """ Base class for all errors returned by the Canvas API. diff --git a/pycanvas/group.py b/pycanvas/group.py index 2dc09986..134db9f7 100644 --- a/pycanvas/group.py +++ b/pycanvas/group.py @@ -1,7 +1,7 @@ from pycanvas.canvas_object import CanvasObject +from pycanvas.exceptions import RequiredFieldMissing from pycanvas.paginated_list import PaginatedList from pycanvas.util import combine_kwargs -from pycanvas.exceptions import RequiredFieldMissing class Group(CanvasObject): diff --git a/pycanvas/util.py b/pycanvas/util.py index ed24ccc3..5343b52d 100644 --- a/pycanvas/util.py +++ b/pycanvas/util.py @@ -1,13 +1,9 @@ -"""A collection of useful methods.""" - - def combine_kwargs(**kwargs): """ Combines a list of keyword arguments into a single dictionary. :rtype: dict """ - def flatten_dict(prefix, key, value): new_prefix = prefix + '[' + str(key) + ']' if isinstance(value, dict): @@ -19,10 +15,11 @@ def flatten_dict(prefix, key, value): return {new_prefix: value} combined_kwargs = {} + # Loop through all kwargs for kw, arg in kwargs.iteritems(): if isinstance(arg, dict): - # If the argument is a dictionary, flatten it. + # If the argument is a dictionary, flatten it for key, value in arg.iteritems(): combined_kwargs.update(flatten_dict(str(kw), key, value)) else: @@ -42,7 +39,6 @@ def obj_or_id(parameter, param_name, object_types): :param object_types: tuple :rtype: int """ - try: return int(parameter) except: From daad8326f7912fdbd4419b3b97196682603b7cfc Mon Sep 17 00:00:00 2001 From: Jesse McBride Date: Tue, 29 Nov 2016 15:09:55 -0500 Subject: [PATCH 114/145] Formatting. --- pycanvas/account.py | 2 ++ pycanvas/canvas_object.py | 1 - pycanvas/util.py | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index 5c4e21f6..1d918616 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -488,6 +488,7 @@ def get_enrollment(self, enrollment_id, **kwargs): class AccountNotification(CanvasObject): + def __str__(self): # pragma: no cover return "subject: %s, message: %s" % ( self.subject, @@ -496,6 +497,7 @@ def __str__(self): # pragma: no cover class AccountReport(CanvasObject): + def __str__(self): # pragma: no cover return "id: %s, report: %s" % ( self.id, diff --git a/pycanvas/canvas_object.py b/pycanvas/canvas_object.py index c929088b..71c76203 100644 --- a/pycanvas/canvas_object.py +++ b/pycanvas/canvas_object.py @@ -69,7 +69,6 @@ def set_attributes(self, attributes): if DATE_PATTERN.match(str(value)): date = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ') self.__setattr__(attribute + '_date', date) - # Non-unicode character. We can skip over this attribute. except UnicodeEncodeError: continue diff --git a/pycanvas/util.py b/pycanvas/util.py index 5343b52d..1ded18f1 100644 --- a/pycanvas/util.py +++ b/pycanvas/util.py @@ -16,10 +16,10 @@ def flatten_dict(prefix, key, value): combined_kwargs = {} - # Loop through all kwargs + # Loop through all kwargs. for kw, arg in kwargs.iteritems(): if isinstance(arg, dict): - # If the argument is a dictionary, flatten it + # If the argument is a dictionary, flatten it. for key, value in arg.iteritems(): combined_kwargs.update(flatten_dict(str(kw), key, value)) else: From b1594b5680b1a5d3f6f47882e9d2ca357236390a Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Tue, 29 Nov 2016 16:12:15 -0500 Subject: [PATCH 115/145] small adjustments to tests --- pycanvas/group.py | 1 - tests/test_group.py | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pycanvas/group.py b/pycanvas/group.py index 908614d9..3233d153 100644 --- a/pycanvas/group.py +++ b/pycanvas/group.py @@ -491,7 +491,6 @@ def delete(self): 'DELETE', 'group_categories/%s' % (self.id) ) - print response return response.json() def list_groups(self): diff --git a/tests/test_group.py b/tests/test_group.py index abf1a07c..00b194d7 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -232,11 +232,13 @@ def test_update(self): # remove_user() def test_remove_user(self): response = self.membership.remove_user(1) + # the response should be an empty dict that evaluates to false assert not response # remove_self() def test_remove_self(self): response = self.membership.remove_self() + # the response should be an empty dict that evaluates to false assert not response @@ -304,7 +306,8 @@ def test_update(self): # delete_category() def test_delete_category(self): response = self.group_category.delete() - return None + # the response should be an empty dict that evaluates to false + assert not response # list_groups() def test_list_groups(self): From 475722954ab19eef2728e4d6df5a560b981ec080 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Wed, 30 Nov 2016 10:48:19 -0500 Subject: [PATCH 116/145] Made overly-specific fixtures more generic. --- tests/fixtures/conversation.json | 12 ++++++------ tests/fixtures/course.json | 4 ++-- tests/test_course.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/fixtures/conversation.json b/tests/fixtures/conversation.json index d0006bcd..1060d432 100644 --- a/tests/fixtures/conversation.json +++ b/tests/fixtures/conversation.json @@ -4,7 +4,7 @@ "endpoint": "conversations/1", "data": { "id": 1, - "subject": "Pokemon Go" + "subject": "Amazing Conversation" }, "status_code": 200 }, @@ -13,7 +13,7 @@ "endpoint": "conversations/2", "data": { "id": 2, - "subject": "Pokemon Go2" + "subject": "Slightly Entertaining Conversation" }, "status_code": 200 }, @@ -23,11 +23,11 @@ "data": [ { "id": 1, - "subject": "Pokemon Go" + "subject": "Amazing Conversation" }, { "id": 2, - "subject": "Agar.io" + "subject": "Slightly Entertaining Conversation" } ], "status_code": 200, @@ -41,11 +41,11 @@ "data": [ { "id": 3, - "subject": "Minecraft" + "subject": "Boring Conversation" }, { "id": 4, - "subject": "Smash Bros" + "subject": "Average Conversation" } ], "status_code": 200 diff --git a/tests/fixtures/course.json b/tests/fixtures/course.json index 08effeba..a364c243 100644 --- a/tests/fixtures/course.json +++ b/tests/fixtures/course.json @@ -226,11 +226,11 @@ }, "get_user_id_type": { "method": "GET", - "endpoint": "courses/1/users/sis_login_id:ab123456", + "endpoint": "courses/1/users/sis_login_id:SISLOGIN", "data": { "id": 123456, "name": "Abby Smith", - "sis_login_id": "ab123456" + "sis_login_id": "SISLOGIN" }, "status_code": 200 }, diff --git a/tests/test_course.py b/tests/test_course.py index 812fc822..b8feb7b0 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -104,7 +104,7 @@ def test_get_user(self): assert hasattr(user, 'name') def test_get_user_id_type(self): - user = self.course.get_user("ab123456", "sis_login_id") + user = self.course.get_user("SISLOGIN", "sis_login_id") assert isinstance(user, User) assert hasattr(user, 'name') From f5914488876079fd276cb5a04a00b00b97ee26d9 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Tue, 6 Dec 2016 10:29:38 -0500 Subject: [PATCH 117/145] found the issues in test_account and test_course that were causing jenkins errors --- tests/test_account.py | 2 +- tests/test_course.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_account.py b/tests/test_account.py index bc2ff133..6a3a2bd8 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -26,7 +26,7 @@ def setUpClass(self): 'activate_role', 'close_notification', 'create', 'create_2', 'create_course', 'create_notification', 'create_role', 'create_subaccount', 'create_user', 'deactivate_role', - 'delete_user', 'enroll_by_id', 'get_by_id', 'get_by_id_2', + 'delete_user', 'get_by_id', 'get_by_id_2', 'get_by_id_3', 'get_courses', 'get_courses_page_2', 'get_external_tools', 'get_external_tools_p2', 'get_role', 'list_groups_context', 'list_groups_context2', 'list_roles', diff --git a/tests/test_course.py b/tests/test_course.py index 3e6342e0..0d54e8a3 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -36,7 +36,7 @@ def setUpClass(self): 'get_user_id_type', 'get_users', 'get_users_p2', 'list_enrollments', 'list_enrollments_2', 'list_modules', 'list_groups_context', 'list_modules2', 'list_sections', 'list_sections2', 'list_quizzes', 'list_quizzes2', - 'list_groups_context2', 'preview_html', 'reactivate_enrollment', 'reset', 'settings', + 'list_groups_context2', 'preview_html', 'reset', 'settings', 'show_front_page', 'update', 'update_settings', 'upload', 'upload_final', 'create_group_category', 'list_group_categories' ], From a728aa7eae9456a91871fc74260fa5adb35ebeb7 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Fri, 9 Dec 2016 08:48:46 -0500 Subject: [PATCH 118/145] Changed instances of GroupCategories to GroupCategory, etc. Only left 'ies' where it referred to multiple --- pycanvas/account.py | 12 ++++++------ pycanvas/course.py | 12 ++++++------ pycanvas/group.py | 10 +++++----- tests/fixtures/group.json | 16 ++++++++-------- tests/test_account.py | 6 +++--- tests/test_course.py | 6 +++--- tests/test_group.py | 26 +++++++++++++------------- tests/test_progress.py | 4 ++-- 8 files changed, 46 insertions(+), 46 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index 6cf2a055..df466f29 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -508,16 +508,16 @@ def create_group_category(self, name, **kwargs): :calls: `POST /api/v1/accounts/:account_id/group_categories \ `_ - :rtype: :class:`pycanvas.group.GroupCategories` + :rtype: :class:`pycanvas.group.GroupCategory` """ - from group import GroupCategories + from group import GroupCategory response = self._requester.request( 'POST', 'accounts/%s/group_categories' % (self.id), **combine_kwargs(**kwargs) ) - return GroupCategories(self._requester, response.json()) + return GroupCategory(self._requester, response.json()) def list_group_categories(self): """ @@ -526,12 +526,12 @@ def list_group_categories(self): :calls: `GET /api/v1/accounts/:account_id/group_categories \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.GroupCategories` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.GroupCategory` """ - from group import GroupCategories + from group import GroupCategory return PaginatedList( - GroupCategories, + GroupCategory, self._requester, 'GET', 'accounts/%s/group_categories' % (self.id) diff --git a/pycanvas/course.py b/pycanvas/course.py index 69b72f87..be0e33df 100644 --- a/pycanvas/course.py +++ b/pycanvas/course.py @@ -698,9 +698,9 @@ def create_group_category(self, name, **kwargs): :calls: `POST /api/v1/courses/:course_id/group_categories \ `_ - :rtype: :class:`pycanvas.group.GroupCategories` + :rtype: :class:`pycanvas.group.GroupCategory` """ - from pycanvas.group import Group, GroupCategories + from pycanvas.group import Group, GroupCategory response = self._requester.request( 'POST', @@ -708,7 +708,7 @@ def create_group_category(self, name, **kwargs): name=name, **combine_kwargs(**kwargs) ) - return GroupCategories(self._requester, response.json()) + return GroupCategory(self._requester, response.json()) def list_group_categories(self): """ @@ -717,12 +717,12 @@ def list_group_categories(self): :calls: `GET /api/v1/courses/:course_id/group_categories \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.GroupCategories` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.GroupCategory` """ - from pycanvas.group import Group, GroupCategories + from pycanvas.group import Group, GroupCategory return PaginatedList( - GroupCategories, + GroupCategory, self._requester, 'GET', 'courses/%s/group_categories' % (self.id) diff --git a/pycanvas/group.py b/pycanvas/group.py index 17f1fcf8..836be675 100644 --- a/pycanvas/group.py +++ b/pycanvas/group.py @@ -425,7 +425,7 @@ def remove_self(self): return response.json() -class GroupCategories(CanvasObject): +class GroupCategory(CanvasObject): def __str__(self): return "id: %s, name: %s" % (self.id, self.name) @@ -453,13 +453,13 @@ def get_category(self, cat_id): :calls: `GET /api/v1/group_categories/:group_category_id \ `_ - :rtype: :class:`pycanvas.group.GroupCategories` + :rtype: :class:`pycanvas.group.GroupCategory` """ response = self._requester.request( 'GET', 'group_categories/%s' % (cat_id) ) - return GroupCategories(self._requester, response.json()) + return GroupCategory(self._requester, response.json()) def update(self, **kwargs): """ @@ -468,14 +468,14 @@ def update(self, **kwargs): :calls: `PUT /api/v1/group_categories/:group_category_id \ `_ - :rtype: :class:`pycanvas.group.GroupCategories` + :rtype: :class:`pycanvas.group.GroupCategory` """ response = self._requester.request( 'PUT', 'group_categories/%s' % (self.id), **combine_kwargs(**kwargs) ) - return GroupCategories(self._requester, response.json()) + return GroupCategory(self._requester, response.json()) def delete(self): """ diff --git a/tests/fixtures/group.json b/tests/fixtures/group.json index 0798d0f3..bd5fcc20 100644 --- a/tests/fixtures/group.json +++ b/tests/fixtures/group.json @@ -379,7 +379,7 @@ }, - "categories_create_group": { + "category_create_group": { "method": "POST", "endpoint": "group_categories/1/groups", "data": { @@ -402,7 +402,7 @@ }, "status_code": 200 }, - "categories_get_category": { + "category_get_category": { "method": "GET", "endpoint": "group_categories/1", "data": { @@ -418,7 +418,7 @@ }, "status_code": 200 }, - "categories_update": { + "category_update": { "method": "PUT", "endpoint": "group_categories/1", "data": { @@ -434,7 +434,7 @@ }, "status_code": 200 }, - "categories_delete_category": { + "category_delete_category": { "method": "DELETE", "endpoint": "group_categories/1", "data": { @@ -442,7 +442,7 @@ }, "status_code": 200 }, - "categories_list_groups": { + "category_list_groups": { "method": "GET", "endpoint": "group_categories/1/groups", "data": [ @@ -491,7 +491,7 @@ ], "status_code": 200 }, - "categories_list_users": { + "category_list_users": { "method": "GET", "endpoint": "group_categories/1/users", "data": [ @@ -542,7 +542,7 @@ ], "status_code": 200 }, - "categories_assign_members_true": { + "category_assign_members_true": { "method": "POST", "endpoint": "group_categories/1/assign_unassigned_members", "data": [ @@ -603,7 +603,7 @@ ], "status_code": 200 }, - "categories_assign_members_false": { + "category_assign_members_false": { "method": "POST", "endpoint": "group_categories/1/assign_unassigned_members", "data": { diff --git a/tests/test_account.py b/tests/test_account.py index 6a3a2bd8..1a408a82 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -9,7 +9,7 @@ from pycanvas.enrollment import Enrollment from pycanvas.external_tool import ExternalTool from pycanvas.exceptions import RequiredFieldMissing -from pycanvas.group import Group, GroupCategories +from pycanvas.group import Group, GroupCategory from pycanvas.user import User from tests import settings from tests.util import register_uris @@ -304,10 +304,10 @@ def test_list_groups_in_context(self): def test_create_group_category(self): name_str = "Shia Laboef" response = self.account.create_group_category(name=name_str) - assert isinstance(response, GroupCategories) + assert isinstance(response, GroupCategory) # list_group_categories() def test_list_group_categories(self): response = self.account.list_group_categories() category_list = [category for category in response] - assert isinstance(category_list[0], GroupCategories) + assert isinstance(category_list[0], GroupCategory) diff --git a/tests/test_course.py b/tests/test_course.py index 0d54e8a3..99d643a2 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -11,7 +11,7 @@ from pycanvas.enrollment import Enrollment from pycanvas.exceptions import ResourceDoesNotExist, RequiredFieldMissing from pycanvas.external_tool import ExternalTool -from pycanvas.group import Group, GroupCategories +from pycanvas.group import Group, GroupCategory from pycanvas.module import Module from pycanvas.quiz import Quiz from pycanvas.section import Section @@ -382,13 +382,13 @@ def test_list_groups_in_context(self): def test_create_group_category(self): name_str = "Shia Laboef" response = self.course.create_group_category(name=name_str) - assert isinstance(response, GroupCategories) + assert isinstance(response, GroupCategory) # list_group_categories() def test_list_group_categories(self): response = self.course.list_group_categories() category_list = [category for category in response] - assert isinstance(category_list[0], GroupCategories) + assert isinstance(category_list[0], GroupCategory) class TestCourseNickname(unittest.TestCase): diff --git a/tests/test_group.py b/tests/test_group.py index 8622b31e..eac843be 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -3,7 +3,7 @@ import requests_mock from pycanvas import Canvas -from pycanvas.group import Group, GroupMembership, GroupCategories +from pycanvas.group import Group, GroupMembership, GroupCategory from pycanvas.course import Page from pycanvas.exceptions import RequiredFieldMissing from tests import settings @@ -242,9 +242,9 @@ def test_remove_self(self): assert not response -class TestGroupCategories(unittest.TestCase): +class TestGroupCategory(unittest.TestCase): """ - Tests GroupCategories Item functionality + Tests GroupCategory Item functionality """ @classmethod def setUpClass(self): @@ -256,14 +256,14 @@ def setUpClass(self): 'get_by_id', 'create_group_category', 'list_group_categories' ], 'group': [ - 'categories_create_group', - 'categories_get_category', - 'categories_update', - 'categories_delete_category', - 'categories_list_groups', - 'categories_list_users', - 'categories_assign_members_true', - 'categories_assign_members_false' + 'category_create_group', + 'category_get_category', + 'category_update', + 'category_delete_category', + 'category_list_groups', + 'category_list_users', + 'category_assign_members_true', + 'category_assign_members_false' ] } @@ -295,13 +295,13 @@ def test_create_group(self): # get_category() def test_get_category(self): response = self.group_category.get_category(1) - assert isinstance(response, GroupCategories) + assert isinstance(response, GroupCategory) # update() def test_update(self): new_name = "Test Update Category" response = self.group_category.update(name=new_name) - assert isinstance(response, GroupCategories) + assert isinstance(response, GroupCategory) # delete_category() def test_delete_category(self): diff --git a/tests/test_progress.py b/tests/test_progress.py index ad393ef5..c42e900d 100644 --- a/tests/test_progress.py +++ b/tests/test_progress.py @@ -5,7 +5,7 @@ import settings from pycanvas.canvas import Canvas from pycanvas.course import Course -from pycanvas.group import Group, GroupCategories +from pycanvas.group import Group, GroupCategory from pycanvas.progress import Progress from util import register_uris @@ -19,7 +19,7 @@ def setUpClass(self): requires = { 'generic': ['not_found'], 'course': ['get_by_id', 'create_group_category'], - 'group': ['categories_create_group', 'categories_assign_members_false'], + 'group': ['category_create_group', 'category_assign_members_false'], 'progress': ['progress_query'] } From c38ce2f19c1459522a9364ccc73280cac95facba Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Fri, 9 Dec 2016 09:32:45 -0500 Subject: [PATCH 119/145] changed group's __str__ method to match standard format --- pycanvas/group.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycanvas/group.py b/pycanvas/group.py index 836be675..05e85861 100644 --- a/pycanvas/group.py +++ b/pycanvas/group.py @@ -368,7 +368,7 @@ def update_membership(self, user_id, **kwargs): class GroupMembership(CanvasObject): def __str__(self): - return "User: %s, Group: %s" % (self.user_id, self.group_id) + return "{} ({})".format(self.user_id, self.group_id) def update(self, mem_id, **kwargs): """ @@ -428,7 +428,7 @@ def remove_self(self): class GroupCategory(CanvasObject): def __str__(self): - return "id: %s, name: %s" % (self.id, self.name) + return "{} ({})".format(self.name, self.id) def create_group(self, **kwargs): """ From 8d986180e2e2e636fa40ba6ed4469a72da9b12e0 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Fri, 9 Dec 2016 09:36:26 -0500 Subject: [PATCH 120/145] added punctuation --- pycanvas/group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycanvas/group.py b/pycanvas/group.py index 05e85861..636da8c0 100644 --- a/pycanvas/group.py +++ b/pycanvas/group.py @@ -162,7 +162,7 @@ def invite(self, invitees): :param invitees: list of user ids :type invitees: integer list - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.groupGroupMembership` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.GroupMembership` """ return PaginatedList( GroupMembership, From 71b77582b4f02d9a963e602adee2d6bdba73161d Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Fri, 9 Dec 2016 09:37:27 -0500 Subject: [PATCH 121/145] correct grammar error --- pycanvas/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index df466f29..f8f6790f 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -485,7 +485,7 @@ def get_enrollment(self, enrollment_id, **kwargs): def list_groups_in_context(self, **kwargs): """ - Return list of active groups for the specified account. + Return a list of active groups for the specified account. :calls: `GET /api/v1/accounts/:account_id/groups \ `_ From 0edf3252ba956b50bc5e8310572cae5336127024 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Fri, 9 Dec 2016 10:57:19 -0500 Subject: [PATCH 122/145] edited comment formatting --- pycanvas/course.py | 4 ++-- pycanvas/group.py | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/pycanvas/course.py b/pycanvas/course.py index be0e33df..2770fb2b 100644 --- a/pycanvas/course.py +++ b/pycanvas/course.py @@ -693,7 +693,7 @@ def list_groups_in_context(self, **kwargs): def create_group_category(self, name, **kwargs): """ - Create a Group Category + Create a group category. :calls: `POST /api/v1/courses/:course_id/group_categories \ `_ @@ -712,7 +712,7 @@ def create_group_category(self, name, **kwargs): def list_group_categories(self): """ - List group categories for a context + List group categories for a context. :calls: `GET /api/v1/courses/:course_id/group_categories \ `_ diff --git a/pycanvas/group.py b/pycanvas/group.py index 636da8c0..61e3202a 100644 --- a/pycanvas/group.py +++ b/pycanvas/group.py @@ -83,6 +83,7 @@ def show_front_page(self): def get_page(self, url): """ Retrieve the contents of a wiki page. + :calls: `GET /api/v1/groups/:group_id/pages/:url \ `_ @@ -260,7 +261,7 @@ def preview_html(self, html): ) return response.json().get('html', '') - # def get_activity_stream(self): + # def get_activity_stream(self): # Not in use # """ # Returns the current user's group-specific activity stream, paginated. @@ -333,7 +334,7 @@ def get_membership(self, user_id, membership_type): def create_membership(self, user_id, **kwargs): """ Join, or request to join, a group, depending on the join_level of the group. - If the membership or join request already exists, then it is simply returned + If the membership or join request already exists, then it is simply returned. :calls: `POST /api/v1/groups/:group_id/memberships \ `_ @@ -368,7 +369,7 @@ def update_membership(self, user_id, **kwargs): class GroupMembership(CanvasObject): def __str__(self): - return "{} ({})".format(self.user_id, self.group_id) + return "{} - {} ({}))".format(self.user_id, self.group_id, self.id) def update(self, mem_id, **kwargs): """ @@ -432,7 +433,7 @@ def __str__(self): def create_group(self, **kwargs): """ - Create a group + Create a group. :calls: `POST /api/v1/group_categories/:group_category_id/groups \ `_ @@ -448,7 +449,7 @@ def create_group(self, **kwargs): def get_category(self, cat_id): """ - Get a single group category + Get a single group category. :calls: `GET /api/v1/group_categories/:group_category_id \ `_ @@ -463,7 +464,7 @@ def get_category(self, cat_id): def update(self, **kwargs): """ - Update a Group Category + Update a group category. :calls: `PUT /api/v1/group_categories/:group_category_id \ `_ @@ -479,7 +480,7 @@ def update(self, **kwargs): def delete(self): """ - Delete a Group Category + Delete a group category. :calls: `DELETE /api/v1/group_categories/:group_category_id \ `_ @@ -494,7 +495,7 @@ def delete(self): def list_groups(self): """ - List groups in group category + List groups in group category. :calls: `GET /api/v1/group_categories/:group_category_id/groups \ `_ @@ -510,7 +511,7 @@ def list_groups(self): def list_users(self, **kwargs): """ - List users in group category + List users in group category. :calls: `GET /api/v1/group_categories/:group_category_id/users \ `_ @@ -528,7 +529,7 @@ def list_users(self, **kwargs): def assign_members(self, sync=False): """ - Assign unassigned members + Assign unassigned members. :calls: `POST /api/v1/group_categories/:group_category_id/assign_unassigned_members \ `_ From 2de1eaa73d3e78ab1fc57cb59e1e09d34ab64720 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Fri, 9 Dec 2016 11:15:51 -0500 Subject: [PATCH 123/145] final comment edits --- pycanvas/group.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pycanvas/group.py b/pycanvas/group.py index 61e3202a..84ce5a1e 100644 --- a/pycanvas/group.py +++ b/pycanvas/group.py @@ -312,12 +312,8 @@ def get_membership(self, user_id, membership_type): """ List users in a group. - if membership_type = 'users' - :calls: `GET /api/v1/groups/:group_id/users/:user_id \ - `_ - - if membership_type = 'memberships' - :calls: `GET /api/v1/groups/:group_id/memberships/:membership_id \ + :calls: `GET /api/v1/groups/:group_id/users/:user_id or \ + /api/v1/groups/:group_id/memberships/:membership_id \ `_ :param invitees: list of user ids From 535bad061dc03f941f89a8cc559313f63051b2f5 Mon Sep 17 00:00:00 2001 From: Adrian Goetz Date: Tue, 13 Dec 2016 14:25:47 -0500 Subject: [PATCH 124/145] addressed Matts comments in diff --- pycanvas/account.py | 5 ++++- pycanvas/course.py | 8 +++++--- pycanvas/progress.py | 3 ++- tests/test_account.py | 4 ++-- tests/test_course.py | 4 ++-- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index f8f6790f..1fe49245 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -483,7 +483,7 @@ def get_enrollment(self, enrollment_id, **kwargs): ) return Enrollment(self._requester, response.json()) - def list_groups_in_context(self, **kwargs): + def list_groups(self, **kwargs): """ Return a list of active groups for the specified account. @@ -508,6 +508,8 @@ def create_group_category(self, name, **kwargs): :calls: `POST /api/v1/accounts/:account_id/group_categories \ `_ + :param name: Name of group category. + :type name: str :rtype: :class:`pycanvas.group.GroupCategory` """ from group import GroupCategory @@ -515,6 +517,7 @@ def create_group_category(self, name, **kwargs): response = self._requester.request( 'POST', 'accounts/%s/group_categories' % (self.id), + name=name, **combine_kwargs(**kwargs) ) return GroupCategory(self._requester, response.json()) diff --git a/pycanvas/course.py b/pycanvas/course.py index 2770fb2b..eb6761bc 100644 --- a/pycanvas/course.py +++ b/pycanvas/course.py @@ -673,7 +673,7 @@ def create_course_section(self, **kwargs): return Section(self._requester, response.json()) - def list_groups_in_context(self, **kwargs): + def list_groups(self, **kwargs): """ Return list of active groups for the specified course. @@ -698,9 +698,11 @@ def create_group_category(self, name, **kwargs): :calls: `POST /api/v1/courses/:course_id/group_categories \ `_ + :param name: Name of the category. + :type name: string :rtype: :class:`pycanvas.group.GroupCategory` """ - from pycanvas.group import Group, GroupCategory + from pycanvas.group import GroupCategory response = self._requester.request( 'POST', @@ -719,7 +721,7 @@ def list_group_categories(self): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.GroupCategory` """ - from pycanvas.group import Group, GroupCategory + from pycanvas.group import GroupCategory return PaginatedList( GroupCategory, diff --git a/pycanvas/progress.py b/pycanvas/progress.py index 90a02274..41b20f46 100644 --- a/pycanvas/progress.py +++ b/pycanvas/progress.py @@ -8,7 +8,7 @@ def __str__(self): def query(self): """ - Return completion and status information about an asynchronous job + Return completion and status information about an asynchronous job. :calls: `GET /api/v1/progress/:id \ `_ @@ -19,4 +19,5 @@ def query(self): 'GET', 'progress/%s' % (self.id) ) + super(Progress, self).set_attributes(response.json()) return Progress(self._requester, response.json()) diff --git a/tests/test_account.py b/tests/test_account.py index 1a408a82..57c0a035 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -293,8 +293,8 @@ def test_get_enrollment(self): assert isinstance(target_enrollment, Enrollment) - def test_list_groups_in_context(self): - groups = self.account.list_groups_in_context() + def test_list_groups(self): + groups = self.account.list_groups() group_list = [group for group in groups] assert isinstance(group_list[0], Group) diff --git a/tests/test_course.py b/tests/test_course.py index 99d643a2..9a8fca37 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -371,8 +371,8 @@ def test_create_course_section(self): assert isinstance(section, Section) - def test_list_groups_in_context(self): - groups = self.course.list_groups_in_context() + def test_list_groups(self): + groups = self.course.list_groups() group_list = [group for group in groups] assert isinstance(group_list[0], Group) From 3d01a3497bbac01406cac730775fa8a8173e46d8 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Tue, 13 Dec 2016 15:55:21 -0500 Subject: [PATCH 125/145] Formatting and style tweaks. --- pycanvas/account.py | 3 ++- pycanvas/course.py | 10 ++++++---- pycanvas/group.py | 41 ++++++++++++++++------------------------- pycanvas/progress.py | 7 +++++-- 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/pycanvas/account.py b/pycanvas/account.py index 1fe49245..8fa4c8e4 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -529,7 +529,8 @@ def list_group_categories(self): :calls: `GET /api/v1/accounts/:account_id/group_categories \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.GroupCategory` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.group.GroupCategory` """ from group import GroupCategory diff --git a/pycanvas/course.py b/pycanvas/course.py index eb6761bc..d3b93153 100644 --- a/pycanvas/course.py +++ b/pycanvas/course.py @@ -411,7 +411,8 @@ def get_modules(self, **kwargs): :calls: `GET /api/v1/courses/:course_id/modules \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.module.Module` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.module.Module` """ from pycanvas.module import Module @@ -622,7 +623,7 @@ def get_page(self, url): `_ :param url: The url for the page. - :type url: string + :type url: str :returns: The specified page. :rtype: :class: `pycanvas.course.Course` """ @@ -699,7 +700,7 @@ def create_group_category(self, name, **kwargs): `_ :param name: Name of the category. - :type name: string + :type name: str :rtype: :class:`pycanvas.group.GroupCategory` """ from pycanvas.group import GroupCategory @@ -719,7 +720,8 @@ def list_group_categories(self): :calls: `GET /api/v1/courses/:course_id/group_categories \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.GroupCategory` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.group.GroupCategory` """ from pycanvas.group import GroupCategory diff --git a/pycanvas/group.py b/pycanvas/group.py index 84ce5a1e..0fdd33b2 100644 --- a/pycanvas/group.py +++ b/pycanvas/group.py @@ -16,8 +16,8 @@ def create_page(self, wiki_page, **kwargs): :calls: `POST /api/v1/groups/:group_id/pages \ `_ - :param title: The title for the page. - :type title: dict + :param wiki_page: Details about the page to create. + :type wiki_page: dict :returns: The created page. :rtype: :class: `pycanvas.page.Page` """ @@ -88,7 +88,7 @@ def get_page(self, url): `_ :param url: The url for the page. - :type url: string + :type url: str :returns: The specified page. :rtype: :class: `pycanvas.groups.Group` """ @@ -110,7 +110,8 @@ def get_pages(self, **kwargs): :calls: `GET /api/v1/groups/:group_id/pages \ `_ - :rtype: :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.page.Page` + :rtype: :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.page.Page` """ from pycanvas.course import Page return PaginatedList( @@ -163,7 +164,8 @@ def invite(self, invitees): :param invitees: list of user ids :type invitees: integer list - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.GroupMembership` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.group.GroupMembership` """ return PaginatedList( GroupMembership, @@ -183,7 +185,8 @@ def list_users(self, **kwargs): :param invitees: list of user ids :type invitees: integer list - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.user.User` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.user.User` """ from pycanvas.user import User return PaginatedList( @@ -261,21 +264,6 @@ def preview_html(self, html): ) return response.json().get('html', '') - # def get_activity_stream(self): # Not in use - # """ - # Returns the current user's group-specific activity stream, paginated. - - # :calls: `GET /api/v1/groups/:group_id/activity_stream \ - # `_ - - # :rtype: list of various objects. - # """ - # response = self._requester.request( - # 'GET', - # 'groups/self/activity_stream/' - # ) - # return response.json() - def get_activity_stream_summary(self): """ Return a summary of the current user's global activity stream. @@ -298,7 +286,8 @@ def list_memberships(self, **kwargs): :calls: `GET /api/v1/groups/:group_id/memberships \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.GroupMembership` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.group.GroupMembership` """ return PaginatedList( GroupMembership, @@ -365,7 +354,7 @@ def update_membership(self, user_id, **kwargs): class GroupMembership(CanvasObject): def __str__(self): - return "{} - {} ({}))".format(self.user_id, self.group_id, self.id) + return "{} - {} ({})".format(self.user_id, self.group_id, self.id) def update(self, mem_id, **kwargs): """ @@ -496,7 +485,8 @@ def list_groups(self): :calls: `GET /api/v1/group_categories/:group_category_id/groups \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.Group` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.group.Group` """ return PaginatedList( Group, @@ -512,7 +502,8 @@ def list_users(self, **kwargs): :calls: `GET /api/v1/group_categories/:group_category_id/users \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.user.User` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.user.User` """ from pycanvas.user import User return PaginatedList( diff --git a/pycanvas/progress.py b/pycanvas/progress.py index 41b20f46..ff50456b 100644 --- a/pycanvas/progress.py +++ b/pycanvas/progress.py @@ -19,5 +19,8 @@ def query(self): 'GET', 'progress/%s' % (self.id) ) - super(Progress, self).set_attributes(response.json()) - return Progress(self._requester, response.json()) + response_json = response.json() + + super(Progress, self).set_attributes(response_json) + + return Progress(self._requester, response_json) From 2d1997b64b799db0099f48d86df512c89cd5d329 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Thu, 15 Dec 2016 10:45:17 -0500 Subject: [PATCH 126/145] Prepping tests for switch to @requests_mock.Mocker() decorator: Removed adapter code, Updated register_uris to use Mocker objects rather than Adapters, Changed mock:// to http:// --- pycanvas/canvas.py | 6 ++---- pycanvas/requester.py | 7 +------ tests/fixtures/account.json | 18 +++++++++--------- tests/fixtures/conversation.json | 2 +- tests/fixtures/course.json | 24 ++++++++++++------------ tests/fixtures/external_tool.json | 8 ++++---- tests/fixtures/group.json | 8 ++++---- tests/fixtures/module.json | 2 +- tests/fixtures/page.json | 2 +- tests/fixtures/paginated_list.json | 6 +++--- tests/fixtures/section.json | 2 +- tests/fixtures/uploader.json | 6 +++--- tests/fixtures/user.json | 26 +++++++++++++------------- tests/settings.py | 2 +- tests/util.py | 12 ++++++------ tests_requirements.txt | 2 +- 16 files changed, 63 insertions(+), 70 deletions(-) diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index a31e3af4..51284d05 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -12,16 +12,14 @@ class Canvas(object): The main class to be instantiated to provide access to Canvas's API. """ - def __init__(self, base_url, access_token, adapter=None): + def __init__(self, base_url, access_token): """ :param base_url: The base URL of the Canvas instance's API. :type base_url: str :param access_token: The API key to authenticate requests with. :type access_token: str - :param adapter: The requests_mock adapter (for testing). - :type adapter: :class:`requests_mock.Adapter` """ - self.__requester = Requester(base_url, access_token, adapter) + self.__requester = Requester(base_url, access_token) def create_account(self, **kwargs): """ diff --git a/pycanvas/requester.py b/pycanvas/requester.py index 16889c58..0889722f 100644 --- a/pycanvas/requester.py +++ b/pycanvas/requester.py @@ -11,22 +11,17 @@ class Requester(object): Responsible for handling HTTP requests. """ - def __init__(self, base_url, access_token, mock_adapter): + def __init__(self, base_url, access_token): """ :param base_url: The base URL of the Canvas instance's API. :type base_url: str :param access_token: The API key to authenticate requests with. :type access_token: str - :param mock_adapter: The requests_mock adapter (for testing). - :type mock_adapter: :class:`requests_mock.Adapter` """ self.base_url = base_url self.access_token = access_token self._session = requests.Session() - if mock_adapter: - self._session.mount('mock', mock_adapter) - def request(self, method, endpoint=None, headers=None, use_auth=True, url=None, **kwargs): """ Make a request to the Canvas API and return the response. diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json index 2e04a7b2..ea189377 100644 --- a/tests/fixtures/account.json +++ b/tests/fixtures/account.json @@ -179,7 +179,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -234,7 +234,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -278,7 +278,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "list_groups_context2": { @@ -362,7 +362,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -393,7 +393,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -426,7 +426,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -469,7 +469,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -502,7 +502,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -603,7 +603,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, diff --git a/tests/fixtures/conversation.json b/tests/fixtures/conversation.json index 1060d432..00b3e684 100644 --- a/tests/fixtures/conversation.json +++ b/tests/fixtures/conversation.json @@ -32,7 +32,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "get_conversations_2": { diff --git a/tests/fixtures/course.json b/tests/fixtures/course.json index b3e90ea0..3061d856 100644 --- a/tests/fixtures/course.json +++ b/tests/fixtures/course.json @@ -49,7 +49,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "get_all_assignments2": { @@ -136,7 +136,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -187,7 +187,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -248,7 +248,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -286,7 +286,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "list_enrollments_2": { @@ -323,7 +323,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "list_quizzes2": { @@ -367,7 +367,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -505,14 +505,14 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "upload": { "method": "POST", "endpoint": "courses/1/files", "data": { - "upload_url": "mock://example.com/api/v1/files/upload_response_upload_url", + "upload_url": "http://example.com/api/v1/files/upload_response_upload_url", "upload_params": { "some_param": "param123", "a_different_param": "param456" @@ -602,7 +602,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "get_pages2": { @@ -677,7 +677,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "list_sections2": { @@ -719,7 +719,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "list_groups_context2": { diff --git a/tests/fixtures/external_tool.json b/tests/fixtures/external_tool.json index 2ba0c28f..0f53e04c 100644 --- a/tests/fixtures/external_tool.json +++ b/tests/fixtures/external_tool.json @@ -6,7 +6,7 @@ "id": 1, "name": "External Tool #1 (Account)", "description": "This is an external tool for an account.", - "url": "mock://www.example.com/ims/lti", + "url": "http://www.example.com/ims/lti", "privacy_level": "anonymous", "created_at": "2015-01-01T01:01:01Z", "updated_at": "2016-06-17T14:20:00Z" @@ -20,7 +20,7 @@ "id": 1, "name": "External Tool #1 (Course)", "description": "This is an external tool in a course.", - "url": "mock://www.example.com/ims/lti", + "url": "http://www.example.com/ims/lti", "privacy_level": "anonymous", "created_at": "2015-01-01T01:01:01Z", "updated_at": "2016-06-17T14:20:00Z" @@ -34,7 +34,7 @@ "id": 2, "name": "External Tool #2 (Course)", "description": "This is an external tool in a course.", - "url": "mock://www.example.com/ims/lti", + "url": "http://www.example.com/ims/lti", "privacy_level": "anonymous", "created_at": "2015-01-01T01:01:01Z", "updated_at": "2016-06-17T14:20:00Z" @@ -47,7 +47,7 @@ "data": { "id": "1", "name": "External Tool #1 (Course)", - "url": "mock://example.com/courses/1/external_tools/sessionless_launch/?verifier=1337" + "url": "http://example.com/courses/1/external_tools/sessionless_launch/?verifier=1337" }, "status_code": 200 }, diff --git a/tests/fixtures/group.json b/tests/fixtures/group.json index bd5fcc20..e387f65a 100644 --- a/tests/fixtures/group.json +++ b/tests/fixtures/group.json @@ -96,7 +96,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "group_get_pages2": { @@ -173,7 +173,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -205,7 +205,7 @@ "method": "POST", "endpoint": "groups/1/files", "data": { - "upload_url": "mock://example.com/api/v1/files/upload_response_upload_url", + "upload_url": "http://example.com/api/v1/files/upload_response_upload_url", "upload_params": { "some_param": "param123", "a_different_param": "param456" @@ -276,7 +276,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, diff --git a/tests/fixtures/module.json b/tests/fixtures/module.json index 98efb38d..9369b5a5 100644 --- a/tests/fixtures/module.json +++ b/tests/fixtures/module.json @@ -52,7 +52,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "list_module_items2": { diff --git a/tests/fixtures/page.json b/tests/fixtures/page.json index 78295ab2..02f2e3eb 100644 --- a/tests/fixtures/page.json +++ b/tests/fixtures/page.json @@ -44,7 +44,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "list_revisions2": { diff --git a/tests/fixtures/paginated_list.json b/tests/fixtures/paginated_list.json index a468e334..904906d2 100644 --- a/tests/fixtures/paginated_list.json +++ b/tests/fixtures/paginated_list.json @@ -45,7 +45,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -78,7 +78,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -96,7 +96,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, diff --git a/tests/fixtures/section.json b/tests/fixtures/section.json index 291201f0..93d0fb9b 100644 --- a/tests/fixtures/section.json +++ b/tests/fixtures/section.json @@ -26,7 +26,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "list_enrollments_2": { diff --git a/tests/fixtures/uploader.json b/tests/fixtures/uploader.json index 08e8179e..22b6c174 100644 --- a/tests/fixtures/uploader.json +++ b/tests/fixtures/uploader.json @@ -3,7 +3,7 @@ "method": "POST", "endpoint": "upload_response", "data": { - "upload_url": "mock://example.com/api/v1/upload_response_upload_url", + "upload_url": "http://example.com/api/v1/upload_response_upload_url", "upload_params": { "some_param": "param123", "a_different_param": "param456" @@ -28,14 +28,14 @@ "method": "POST", "endpoint": "upload_response_no_upload_params", "data": { - "upload_url": "mock://example.com/api/v1/upload_response_upload_url" + "upload_url": "http://example.com/api/v1/upload_response_upload_url" } }, "upload_response_fail": { "method": "POST", "endpoint": "upload_response_fail", "data": { - "upload_url": "mock://example.com/api/v1/upload_fail", + "upload_url": "http://example.com/api/v1/upload_fail", "upload_params": { "some_param": "param123", "a_different_param": "param456" diff --git a/tests/fixtures/user.json b/tests/fixtures/user.json index 640ef413..c15c1d23 100644 --- a/tests/fixtures/user.json +++ b/tests/fixtures/user.json @@ -32,7 +32,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -117,7 +117,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "course_nicknames_delete": { @@ -161,7 +161,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -233,7 +233,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "get_user_assignments2": { @@ -270,7 +270,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "list_enrollments_2": { @@ -303,7 +303,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "list_groups2": { @@ -345,7 +345,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -372,19 +372,19 @@ "data": [ { "id": "123fb561-c9ce-7ed9-8e42-31e0aab47e18", - "url": "mock://example.com/test", + "url": "http://example.com/test", "created_at": "2013-01-01T12:00:00Z", "context_type": "Course" }, { "id": "4c7f44f9-41b9-ff59-03ad-3215cb246966", - "url": "mock://example.com/hello", + "url": "http://example.com/hello", "created_at": "2014-01-01T12:00:00Z", "context_type": "User" } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -394,13 +394,13 @@ "data": [ { "id": "77f111c9-5b0b-46cb-92a2-34108ebe7b5b", - "url": "mock://example.com/login", + "url": "http://example.com/login", "created_at": "2015-01-01T12:00:00Z", "context_type": null }, { "id": "69ec8b6f-e123-2af8-dafb-561fdc711f6b", - "url": "mock://example.com/logout", + "url": "http://example.com/logout", "created_at": "2016-01-01T12:00:00Z", "context_type": "UserProfile" } @@ -475,7 +475,7 @@ "method": "POST", "endpoint": "users/1/files", "data": { - "upload_url": "mock://example.com/api/v1/files/upload_response_upload_url", + "upload_url": "http://example.com/api/v1/files/upload_response_upload_url", "upload_params": { "some_param": "param123", "a_different_param": "param456" diff --git a/tests/settings.py b/tests/settings.py index 4ec0b13f..636763d8 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -1,4 +1,4 @@ -BASE_URL = 'mock://example.com/api/v1/' +BASE_URL = 'http://example.com/api/v1/' API_KEY = '123' INVALID_ID = 9001 diff --git a/tests/util.py b/tests/util.py index f01befab..9bcf1d7e 100644 --- a/tests/util.py +++ b/tests/util.py @@ -3,14 +3,14 @@ import requests_mock -def register_uris(base_url, requirements, adapter): +def register_uris(base_url, requirements, requests_mocker): """ - Given a list of required fixtures and an adapter object, register each fixture - as a uri with the adapter. + Given a list of required fixtures and an requests_mocker object, + register each fixture as a uri with the mocker. :param base_url: str :param requirements: dict - :param adapter: requests_mock.Adapter + :param requests_mocker: requests_mock.mocker.Mocker """ for fixture, objects in requirements.iteritems(): @@ -26,11 +26,11 @@ def register_uris(base_url, requirements, adapter): url = requests_mock.ANY if obj['endpoint'] == 'ANY' else base_url + obj['endpoint'] try: - adapter.register_uri( + requests_mocker.register_uri( method, url, json=obj.get('data'), - status_code=obj.get('status_code'), + status_code=obj.get('status_code', 200), headers=obj.get('headers', {}) ) except Exception as e: diff --git a/tests_requirements.txt b/tests_requirements.txt index b9609a58..1261c908 100644 --- a/tests_requirements.txt +++ b/tests_requirements.txt @@ -1,4 +1,4 @@ -r requirements.txt coverage==4.1 -requests-mock==1.0.0 +requests-mock==1.2.0 From 46c550be75867e121ebf664c332ff1e548239f4b Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Thu, 15 Dec 2016 10:48:33 -0500 Subject: [PATCH 127/145] Updated test_account to new mocker format. Minor text fixes in fixtures. --- tests/fixtures/account.json | 27 ++---- tests/fixtures/course.json | 2 +- tests/fixtures/user.json | 3 +- tests/test_account.py | 189 ++++++++++++++++++++++-------------- tests/test_course.py | 2 +- tests/test_group.py | 2 +- tests/test_progress.py | 2 +- 7 files changed, 130 insertions(+), 97 deletions(-) diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json index ea189377..139ac7b3 100644 --- a/tests/fixtures/account.json +++ b/tests/fixtures/account.json @@ -125,24 +125,9 @@ }, "get_by_id_2": { "method": "GET", - "endpoint": "accounts/100", - "data": { - "id": 100, - "name": "Old Name", - "parent_account_id": null, - "root_account_id": null, - "default_storage_quota_mb": 500, - "default_user_storage_quota_mb": 50, - "default_group_storage_quota_mb": 50, - "default_time_zone": "America/Denver" - }, - "status_code": 200 - }, - "get_by_id_3": { - "method": "GET", - "endpoint": "accounts/101", + "endpoint": "accounts/1", "data": { - "id": 101, + "id": 1, "name": "Old Name", "parent_account_id": null, "root_account_id": null, @@ -523,9 +508,9 @@ }, "update": { "method": "PUT", - "endpoint": "accounts/100", + "endpoint": "accounts/1", "data": { - "id": 100, + "id": 1, "name": "Updated Name", "parent_account_id": null, "root_account_id": null, @@ -538,7 +523,7 @@ }, "update_fail": { "method": "PUT", - "endpoint": "accounts/101", + "endpoint": "accounts/1", "data": {}, "status_code": 200 }, @@ -547,7 +532,7 @@ "endpoint": "accounts/1/group_categories", "data": { "id": 1, - "name": "Shia Laboef", + "name": "Test String", "role": "communities", "self_signup": null, "auto_leader": null, diff --git a/tests/fixtures/course.json b/tests/fixtures/course.json index 3061d856..5f7b7cd7 100644 --- a/tests/fixtures/course.json +++ b/tests/fixtures/course.json @@ -742,7 +742,7 @@ "endpoint": "courses/1/group_categories", "data": { "id": 1, - "name": "Shia Laboef", + "name": "Test String", "role": "communities", "self_signup": null, "auto_leader": null, diff --git a/tests/fixtures/user.json b/tests/fixtures/user.json index c15c1d23..ccbbc139 100644 --- a/tests/fixtures/user.json +++ b/tests/fixtures/user.json @@ -196,7 +196,8 @@ "data": { "id": 1, "name": "John Doe" - } + }, + "status_code": 200 }, "get_by_id_2": { "method": "GET", diff --git a/tests/test_account.py b/tests/test_account.py index 57c0a035..24c1ebc0 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -15,47 +15,30 @@ from tests.util import register_uris +@requests_mock.Mocker() class TestAccount(unittest.TestCase): """ Tests Account methods. """ @classmethod - def setUpClass(self): - requires = { - 'account': [ - 'activate_role', 'close_notification', 'create', 'create_2', - 'create_course', 'create_notification', 'create_role', - 'create_subaccount', 'create_user', 'deactivate_role', - 'delete_user', 'get_by_id', 'get_by_id_2', - 'get_by_id_3', 'get_courses', 'get_courses_page_2', - 'get_external_tools', 'get_external_tools_p2', 'get_role', - 'list_groups_context', 'list_groups_context2', 'list_roles', - 'list_roles_2', 'reports', 'reports_page_2', 'report_index', - 'report_index_page_2', 'subaccounts', 'subaccounts_page_2', - 'users', 'users_page_2', 'user_notifs', 'user_notifs_page_2', - 'update', 'update_fail', 'update_role', 'create_group_category', - 'list_group_categories' - ], - 'enrollment': ['get_by_id'], - 'external_tool': ['get_by_id_account'], - 'user': ['get_by_id'] - } - - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, {'generic': ['not_found']}, adapter) - register_uris(settings.BASE_URL, requires, adapter) + def setUp(self): + with requests_mock.Mocker() as m: + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + requires = {'account': ['get_by_id'], 'user': ['get_by_id']} + register_uris(settings.BASE_URL, requires, m) - self.account = self.canvas.get_account(1) - self.user = self.canvas.get_user(1) + self.account = self.canvas.get_account(1) + self.user = self.canvas.get_user(1) # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.account) assert isinstance(string, str) # close_notification_for_user() - def test_close_notification_for_user_id(self): + def test_close_notification_for_user_id(self, m): + register_uris(settings.BASE_URL, {'account': ['close_notification']}, m) + user_id = self.user.id notif_id = 1 closed_notif = self.account.close_notification_for_user(user_id, notif_id) @@ -63,26 +46,34 @@ def test_close_notification_for_user_id(self): assert isinstance(closed_notif, AccountNotification) assert hasattr(closed_notif, 'subject') - def test_close_notification_for_user_obj(self): + def test_close_notification_for_user_obj(self, m): + register_uris(settings.BASE_URL, {'account': ['close_notification']}, m) + notif_id = 1 self.account.close_notification_for_user(self.user, notif_id) # create_account() - def test_create_account(self): + def test_create_account(self, m): + register_uris(settings.BASE_URL, {'account': ['create_2']}, m) + new_account = self.account.create_account() assert isinstance(new_account, Account) assert hasattr(new_account, 'id') # create_course() - def test_create_course(self): + def test_create_course(self, m): + register_uris(settings.BASE_URL, {'account': ['create_course']}, m) + course = self.account.create_course() assert isinstance(course, Course) assert hasattr(course, 'name') # create_subaccount() - def test_create_subaccount(self): + def test_create_subaccount(self, m): + register_uris(settings.BASE_URL, {'account': ['create_subaccount']}, m) + subaccount_name = "New Subaccount" subaccount = self.account.create_subaccount({'name': subaccount_name}) @@ -92,12 +83,14 @@ def test_create_subaccount(self): assert hasattr(subaccount, 'root_account_id') assert subaccount.root_account_id == self.account.id - def test_create_course_missing_field(self): + def test_create_course_missing_field(self, m): with self.assertRaises(RequiredFieldMissing): self.account.create_subaccount({}) # create_user() - def test_create_user(self): + def test_create_user(self, m): + register_uris(settings.BASE_URL, {'account': ['create_user']}, m) + unique_id = 123456 user = self.account.create_user({'unique_id': unique_id}) @@ -105,12 +98,14 @@ def test_create_user(self): assert hasattr(user, 'unique_id') assert user.unique_id == unique_id - def test_create_user_missing_field(self): + def test_create_user_missing_field(self, m): with self.assertRaises(RequiredFieldMissing): self.account.create_user({}) # create_notification() - def test_create_notification(self): + def test_create_notification(self, m): + register_uris(settings.BASE_URL, {'account': ['create_notification']}, m) + subject = 'Subject' notif_dict = { 'subject': subject, @@ -126,25 +121,32 @@ def test_create_notification(self): assert hasattr(notif, 'start_at_date') assert isinstance(notif.start_at_date, datetime.datetime) - def test_create_notification_missing_field(self): + def test_create_notification_missing_field(self, m): with self.assertRaises(RequiredFieldMissing): self.account.create_notification({}) # delete_user() - def test_delete_user_id(self): + def test_delete_user_id(self, m): + register_uris(settings.BASE_URL, {'account': ['delete_user']}, m) + deleted_user = self.account.delete_user(self.user.id) assert isinstance(deleted_user, User) assert hasattr(deleted_user, 'name') - def test_delete_user_obj(self): + def test_delete_user_obj(self, m): + register_uris(settings.BASE_URL, {'account': ['delete_user']}, m) + deleted_user = self.account.delete_user(self.user) assert isinstance(deleted_user, User) assert hasattr(deleted_user, 'name') # get_courses() - def test_get_courses(self): + def test_get_courses(self, m): + required = {'account': ['get_courses', 'get_courses_page_2']} + register_uris(settings.BASE_URL, required, m) + courses = self.account.get_courses() course_list = [course for course in courses] @@ -153,14 +155,20 @@ def test_get_courses(self): assert hasattr(course_list[0], 'name') # get_external_tool() - def test_get_external_tool(self): + def test_get_external_tool(self, m): + required = {'external_tool': ['get_by_id_account']} + register_uris(settings.BASE_URL, required, m) + tool = self.account.get_external_tool(1) assert isinstance(tool, ExternalTool) assert hasattr(tool, 'name') # get_external_tools() - def test_get_external_tools(self): + def test_get_external_tools(self, m): + required = {'account': ['get_external_tools', 'get_external_tools_p2']} + register_uris(settings.BASE_URL, required, m) + tools = self.account.get_external_tools() tool_list = [tool for tool in tools] @@ -168,7 +176,10 @@ def test_get_external_tools(self): assert len(tool_list) == 4 # get_index_of_reports() - def test_get_index_of_reports(self): + def test_get_index_of_reports(self, m): + required = {'account': ['report_index', 'report_index_page_2']} + register_uris(settings.BASE_URL, required, m) + reports_index = self.account.get_index_of_reports("sis_export_csv") reports_index_list = [index for index in reports_index] @@ -177,7 +188,10 @@ def test_get_index_of_reports(self): assert hasattr(reports_index_list[0], 'id') # get_reports() - def test_get_reports(self): + def test_get_reports(self, m): + required = {'account': ['reports', 'reports_page_2']} + register_uris(settings.BASE_URL, required, m) + reports = self.account.get_reports() reports_list = [report for report in reports] @@ -186,7 +200,10 @@ def test_get_reports(self): assert hasattr(reports_list[0], 'id') # get_subaccounts() - def test_get_subaccounts(self): + def test_get_subaccounts(self, m): + required = {'account': ['subaccounts', 'subaccounts_page_2']} + register_uris(settings.BASE_URL, required, m) + subaccounts = self.account.get_subaccounts() subaccounts_list = [account for account in subaccounts] @@ -195,7 +212,10 @@ def test_get_subaccounts(self): assert hasattr(subaccounts_list[0], 'name') # get_users() - def test_get_users(self): + def test_get_users(self, m): + required = {'account': ['users', 'users_page_2']} + register_uris(settings.BASE_URL, required, m) + users = self.account.get_users() user_list = [user for user in users] @@ -204,7 +224,10 @@ def test_get_users(self): assert hasattr(user_list[0], 'name') # get_user_notifications() - def test_get_user_notifications_id(self): + def test_get_user_notifications_id(self, m): + required = {'account': ['user_notifs', 'user_notifs_page_2']} + register_uris(settings.BASE_URL, required, m) + user_notifs = self.account.get_user_notifications(self.user.id) notif_list = [notif for notif in user_notifs] @@ -212,7 +235,10 @@ def test_get_user_notifications_id(self): assert isinstance(user_notifs[0], AccountNotification) assert hasattr(user_notifs[0], 'subject') - def test_get_user_notifications_obj(self): + def test_get_user_notifications_obj(self, m): + required = {'account': ['user_notifs', 'user_notifs_page_2']} + register_uris(settings.BASE_URL, required, m) + user_notifs = self.account.get_user_notifications(self.user) notif_list = [notif for notif in user_notifs] @@ -221,29 +247,31 @@ def test_get_user_notifications_obj(self): assert hasattr(user_notifs[0], 'subject') # update() - def test_update(self): - account = self.canvas.get_account(100) - assert account.name == 'Old Name' + def test_update(self, m): + register_uris(settings.BASE_URL, {'account': ['update']}, m) + + self.assertEqual(self.account.name, 'Canvas Account') new_name = 'Updated Name' update_account_dict = {'name': new_name} - success = account.update(account=update_account_dict) + self.assertTrue(self.account.update(account=update_account_dict)) + self.assertEqual(self.account.name, new_name) - assert success - assert account.name == new_name + def test_update_fail(self, m): + register_uris(settings.BASE_URL, {'account': ['update_fail']}, m) - def test_update_fail(self): - account = self.canvas.get_account(101) - assert account.name == 'Old Name' + self.assertEqual(self.account.name, 'Canvas Account') new_name = 'Updated Name' update_account_dict = {'name': new_name} - success = account.update(account=update_account_dict) - assert not success + self.assertFalse(self.account.update(account=update_account_dict)) + + def test_list_roles(self, m): + requires = {'account': ['list_roles', 'list_roles_2']} + register_uris(settings.BASE_URL, requires, m) - def test_list_roles(self): roles = self.account.list_roles() role_list = [role for role in roles] @@ -252,35 +280,45 @@ def test_list_roles(self): assert hasattr(role_list[0], 'role') assert hasattr(role_list[0], 'label') - def test_get_role(self): + def test_get_role(self, m): + register_uris(settings.BASE_URL, {'account': ['get_role']}, m) + target_role = self.account.get_role(2) assert isinstance(target_role, Role) assert hasattr(target_role, 'role') assert hasattr(target_role, 'label') - def test_create_role(self): + def test_create_role(self, m): + register_uris(settings.BASE_URL, {'account': ['create_role']}, m) + new_role = self.account.create_role(1) assert isinstance(new_role, Role) assert hasattr(new_role, 'role') assert hasattr(new_role, 'label') - def test_deactivate_role(self): + def test_deactivate_role(self, m): + register_uris(settings.BASE_URL, {'account': ['deactivate_role']}, m) + old_role = self.account.deactivate_role(2) assert isinstance(old_role, Role) assert hasattr(old_role, 'role') assert hasattr(old_role, 'label') - def test_activate_role(self): + def test_activate_role(self, m): + register_uris(settings.BASE_URL, {'account': ['activate_role']}, m) + activated_role = self.account.activate_role(2) assert isinstance(activated_role, Role) assert hasattr(activated_role, 'role') assert hasattr(activated_role, 'label') - def test_update_role(self): + def test_update_role(self, m): + register_uris(settings.BASE_URL, {'account': ['update_role']}, m) + updated_role = self.account.update_role(2) assert isinstance(updated_role, Role) @@ -288,12 +326,17 @@ def test_update_role(self): assert hasattr(updated_role, 'label') # get_enrollment() - def test_get_enrollment(self): + def test_get_enrollment(self, m): + register_uris(settings.BASE_URL, {'enrollment': ['get_by_id']}, m) + target_enrollment = self.account.get_enrollment(1) assert isinstance(target_enrollment, Enrollment) - def test_list_groups(self): + def test_list_groups(self, m): + requires = {'account': ['list_groups_context', 'list_groups_context2']} + register_uris(settings.BASE_URL, requires, m) + groups = self.account.list_groups() group_list = [group for group in groups] @@ -301,13 +344,17 @@ def test_list_groups(self): assert len(group_list) == 4 # create_group_category() - def test_create_group_category(self): - name_str = "Shia Laboef" + def test_create_group_category(self, m): + register_uris(settings.BASE_URL, {'account': ['create_group_category']}, m) + + name_str = "Test String" response = self.account.create_group_category(name=name_str) assert isinstance(response, GroupCategory) # list_group_categories() - def test_list_group_categories(self): + def test_list_group_categories(self, m): + register_uris(settings.BASE_URL, {'account': ['list_group_categories']}, m) + response = self.account.list_group_categories() category_list = [category for category in response] assert isinstance(category_list[0], GroupCategory) diff --git a/tests/test_course.py b/tests/test_course.py index 9a8fca37..b5e000d9 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -380,7 +380,7 @@ def test_list_groups(self): # create_group_category() def test_create_group_category(self): - name_str = "Shia Laboef" + name_str = "Test String" response = self.course.create_group_category(name=name_str) assert isinstance(response, GroupCategory) diff --git a/tests/test_group.py b/tests/test_group.py index eac843be..38db35bb 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -277,7 +277,7 @@ def setUpClass(self): register_uris(settings.BASE_URL, requires, adapter) self.course = self.canvas.get_course(1) - self.group_category = self.course.create_group_category("Shia Laboef") + self.group_category = self.course.create_group_category("Test String") # __str__() def test__str__(self): diff --git a/tests/test_progress.py b/tests/test_progress.py index c42e900d..cfdf9b10 100644 --- a/tests/test_progress.py +++ b/tests/test_progress.py @@ -28,7 +28,7 @@ def setUpClass(self): register_uris(settings.BASE_URL, requires, adapter) self.course = self.canvas.get_course(1) - self.group_category = self.course.create_group_category("Shia Laboef") + self.group_category = self.course.create_group_category("Test String") self.progress = self.group_category.assign_members() From 8217eb7149561433ffe542b7111f3de68896268c Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Thu, 15 Dec 2016 11:11:23 -0500 Subject: [PATCH 128/145] Removed redundant docstring. Moved call of settings.BASE_URL to register_uris. --- tests/test_account.py | 66 +++++++++++++++++++++---------------------- tests/util.py | 6 ++-- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/tests/test_account.py b/tests/test_account.py index 24c1ebc0..ba718bd4 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -17,15 +17,13 @@ @requests_mock.Mocker() class TestAccount(unittest.TestCase): - """ - Tests Account methods. - """ + @classmethod def setUp(self): with requests_mock.Mocker() as m: self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) requires = {'account': ['get_by_id'], 'user': ['get_by_id']} - register_uris(settings.BASE_URL, requires, m) + register_uris(requires, m) self.account = self.canvas.get_account(1) self.user = self.canvas.get_user(1) @@ -37,7 +35,7 @@ def test__str__(self, m): # close_notification_for_user() def test_close_notification_for_user_id(self, m): - register_uris(settings.BASE_URL, {'account': ['close_notification']}, m) + register_uris({'account': ['close_notification']}, m) user_id = self.user.id notif_id = 1 @@ -47,14 +45,14 @@ def test_close_notification_for_user_id(self, m): assert hasattr(closed_notif, 'subject') def test_close_notification_for_user_obj(self, m): - register_uris(settings.BASE_URL, {'account': ['close_notification']}, m) + register_uris({'account': ['close_notification']}, m) notif_id = 1 self.account.close_notification_for_user(self.user, notif_id) # create_account() def test_create_account(self, m): - register_uris(settings.BASE_URL, {'account': ['create_2']}, m) + register_uris({'account': ['create_2']}, m) new_account = self.account.create_account() @@ -63,7 +61,7 @@ def test_create_account(self, m): # create_course() def test_create_course(self, m): - register_uris(settings.BASE_URL, {'account': ['create_course']}, m) + register_uris({'account': ['create_course']}, m) course = self.account.create_course() @@ -72,7 +70,7 @@ def test_create_course(self, m): # create_subaccount() def test_create_subaccount(self, m): - register_uris(settings.BASE_URL, {'account': ['create_subaccount']}, m) + register_uris({'account': ['create_subaccount']}, m) subaccount_name = "New Subaccount" subaccount = self.account.create_subaccount({'name': subaccount_name}) @@ -89,7 +87,7 @@ def test_create_course_missing_field(self, m): # create_user() def test_create_user(self, m): - register_uris(settings.BASE_URL, {'account': ['create_user']}, m) + register_uris({'account': ['create_user']}, m) unique_id = 123456 user = self.account.create_user({'unique_id': unique_id}) @@ -104,7 +102,7 @@ def test_create_user_missing_field(self, m): # create_notification() def test_create_notification(self, m): - register_uris(settings.BASE_URL, {'account': ['create_notification']}, m) + register_uris({'account': ['create_notification']}, m) subject = 'Subject' notif_dict = { @@ -127,7 +125,7 @@ def test_create_notification_missing_field(self, m): # delete_user() def test_delete_user_id(self, m): - register_uris(settings.BASE_URL, {'account': ['delete_user']}, m) + register_uris({'account': ['delete_user']}, m) deleted_user = self.account.delete_user(self.user.id) @@ -135,7 +133,7 @@ def test_delete_user_id(self, m): assert hasattr(deleted_user, 'name') def test_delete_user_obj(self, m): - register_uris(settings.BASE_URL, {'account': ['delete_user']}, m) + register_uris({'account': ['delete_user']}, m) deleted_user = self.account.delete_user(self.user) @@ -145,7 +143,7 @@ def test_delete_user_obj(self, m): # get_courses() def test_get_courses(self, m): required = {'account': ['get_courses', 'get_courses_page_2']} - register_uris(settings.BASE_URL, required, m) + register_uris(required, m) courses = self.account.get_courses() @@ -157,7 +155,7 @@ def test_get_courses(self, m): # get_external_tool() def test_get_external_tool(self, m): required = {'external_tool': ['get_by_id_account']} - register_uris(settings.BASE_URL, required, m) + register_uris(required, m) tool = self.account.get_external_tool(1) @@ -167,7 +165,7 @@ def test_get_external_tool(self, m): # get_external_tools() def test_get_external_tools(self, m): required = {'account': ['get_external_tools', 'get_external_tools_p2']} - register_uris(settings.BASE_URL, required, m) + register_uris(required, m) tools = self.account.get_external_tools() tool_list = [tool for tool in tools] @@ -178,7 +176,7 @@ def test_get_external_tools(self, m): # get_index_of_reports() def test_get_index_of_reports(self, m): required = {'account': ['report_index', 'report_index_page_2']} - register_uris(settings.BASE_URL, required, m) + register_uris(required, m) reports_index = self.account.get_index_of_reports("sis_export_csv") @@ -190,7 +188,7 @@ def test_get_index_of_reports(self, m): # get_reports() def test_get_reports(self, m): required = {'account': ['reports', 'reports_page_2']} - register_uris(settings.BASE_URL, required, m) + register_uris(required, m) reports = self.account.get_reports() @@ -202,7 +200,7 @@ def test_get_reports(self, m): # get_subaccounts() def test_get_subaccounts(self, m): required = {'account': ['subaccounts', 'subaccounts_page_2']} - register_uris(settings.BASE_URL, required, m) + register_uris(required, m) subaccounts = self.account.get_subaccounts() @@ -214,7 +212,7 @@ def test_get_subaccounts(self, m): # get_users() def test_get_users(self, m): required = {'account': ['users', 'users_page_2']} - register_uris(settings.BASE_URL, required, m) + register_uris(required, m) users = self.account.get_users() @@ -226,7 +224,7 @@ def test_get_users(self, m): # get_user_notifications() def test_get_user_notifications_id(self, m): required = {'account': ['user_notifs', 'user_notifs_page_2']} - register_uris(settings.BASE_URL, required, m) + register_uris(required, m) user_notifs = self.account.get_user_notifications(self.user.id) @@ -237,7 +235,7 @@ def test_get_user_notifications_id(self, m): def test_get_user_notifications_obj(self, m): required = {'account': ['user_notifs', 'user_notifs_page_2']} - register_uris(settings.BASE_URL, required, m) + register_uris(required, m) user_notifs = self.account.get_user_notifications(self.user) @@ -248,7 +246,7 @@ def test_get_user_notifications_obj(self, m): # update() def test_update(self, m): - register_uris(settings.BASE_URL, {'account': ['update']}, m) + register_uris({'account': ['update']}, m) self.assertEqual(self.account.name, 'Canvas Account') @@ -259,7 +257,7 @@ def test_update(self, m): self.assertEqual(self.account.name, new_name) def test_update_fail(self, m): - register_uris(settings.BASE_URL, {'account': ['update_fail']}, m) + register_uris({'account': ['update_fail']}, m) self.assertEqual(self.account.name, 'Canvas Account') @@ -270,7 +268,7 @@ def test_update_fail(self, m): def test_list_roles(self, m): requires = {'account': ['list_roles', 'list_roles_2']} - register_uris(settings.BASE_URL, requires, m) + register_uris(requires, m) roles = self.account.list_roles() role_list = [role for role in roles] @@ -281,7 +279,7 @@ def test_list_roles(self, m): assert hasattr(role_list[0], 'label') def test_get_role(self, m): - register_uris(settings.BASE_URL, {'account': ['get_role']}, m) + register_uris({'account': ['get_role']}, m) target_role = self.account.get_role(2) @@ -290,7 +288,7 @@ def test_get_role(self, m): assert hasattr(target_role, 'label') def test_create_role(self, m): - register_uris(settings.BASE_URL, {'account': ['create_role']}, m) + register_uris({'account': ['create_role']}, m) new_role = self.account.create_role(1) @@ -299,7 +297,7 @@ def test_create_role(self, m): assert hasattr(new_role, 'label') def test_deactivate_role(self, m): - register_uris(settings.BASE_URL, {'account': ['deactivate_role']}, m) + register_uris({'account': ['deactivate_role']}, m) old_role = self.account.deactivate_role(2) @@ -308,7 +306,7 @@ def test_deactivate_role(self, m): assert hasattr(old_role, 'label') def test_activate_role(self, m): - register_uris(settings.BASE_URL, {'account': ['activate_role']}, m) + register_uris({'account': ['activate_role']}, m) activated_role = self.account.activate_role(2) @@ -317,7 +315,7 @@ def test_activate_role(self, m): assert hasattr(activated_role, 'label') def test_update_role(self, m): - register_uris(settings.BASE_URL, {'account': ['update_role']}, m) + register_uris({'account': ['update_role']}, m) updated_role = self.account.update_role(2) @@ -327,7 +325,7 @@ def test_update_role(self, m): # get_enrollment() def test_get_enrollment(self, m): - register_uris(settings.BASE_URL, {'enrollment': ['get_by_id']}, m) + register_uris({'enrollment': ['get_by_id']}, m) target_enrollment = self.account.get_enrollment(1) @@ -335,7 +333,7 @@ def test_get_enrollment(self, m): def test_list_groups(self, m): requires = {'account': ['list_groups_context', 'list_groups_context2']} - register_uris(settings.BASE_URL, requires, m) + register_uris(requires, m) groups = self.account.list_groups() group_list = [group for group in groups] @@ -345,7 +343,7 @@ def test_list_groups(self, m): # create_group_category() def test_create_group_category(self, m): - register_uris(settings.BASE_URL, {'account': ['create_group_category']}, m) + register_uris({'account': ['create_group_category']}, m) name_str = "Test String" response = self.account.create_group_category(name=name_str) @@ -353,7 +351,7 @@ def test_create_group_category(self, m): # list_group_categories() def test_list_group_categories(self, m): - register_uris(settings.BASE_URL, {'account': ['list_group_categories']}, m) + register_uris({'account': ['list_group_categories']}, m) response = self.account.list_group_categories() category_list = [category for category in response] diff --git a/tests/util.py b/tests/util.py index 9bcf1d7e..e4a621df 100644 --- a/tests/util.py +++ b/tests/util.py @@ -2,8 +2,10 @@ import requests_mock +from tests import settings -def register_uris(base_url, requirements, requests_mocker): + +def register_uris(requirements, requests_mocker): """ Given a list of required fixtures and an requests_mocker object, register each fixture as a uri with the mocker. @@ -23,7 +25,7 @@ def register_uris(base_url, requirements, requests_mocker): obj = data.get(obj) method = requests_mock.ANY if obj['method'] == 'ANY' else obj['method'] - url = requests_mock.ANY if obj['endpoint'] == 'ANY' else base_url + obj['endpoint'] + url = requests_mock.ANY if obj['endpoint'] == 'ANY' else settings.BASE_URL + obj['endpoint'] try: requests_mocker.register_uri( From 8c145d324d1063f89638c74f8b6cafd62fd7427a Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Thu, 15 Dec 2016 12:46:53 -0500 Subject: [PATCH 129/145] Converted test_assignment --- tests/test_account.py | 3 ++- tests/test_assignment.py | 42 ++++++++++++++++++---------------------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/tests/test_account.py b/tests/test_account.py index ba718bd4..7e51c315 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -20,8 +20,9 @@ class TestAccount(unittest.TestCase): @classmethod def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + with requests_mock.Mocker() as m: - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) requires = {'account': ['get_by_id'], 'user': ['get_by_id']} register_uris(requires, m) diff --git a/tests/test_assignment.py b/tests/test_assignment.py index f2356747..cdac2984 100644 --- a/tests/test_assignment.py +++ b/tests/test_assignment.py @@ -1,50 +1,46 @@ import unittest + import requests_mock -import settings from pycanvas import Canvas from pycanvas.assignment import Assignment +from tests import settings from tests.util import register_uris +@requests_mock.Mocker() class TestAssignment(unittest.TestCase): - """ - Tests Assignment functionality. - """ + @classmethod - def setUpClass(self): - requires = { - 'assignment': ['edit_assignment', 'delete_assignment'], - 'course': ['get_by_id', 'get_assignment_by_id'], - 'user': ['get_by_id'] - } + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) + with requests_mock.Mocker() as m: + register_uris({'course': ['get_by_id', 'get_assignment_by_id']}, m) - self.course = self.canvas.get_course(1) + self.course = self.canvas.get_course(1) + self.assignment = self.course.get_assignment(5) # edit() - def test_edit_assignment(self): + def test_edit_assignment(self, m): + register_uris({'assignment': ['edit_assignment']}, m) + name = 'New Name' - assignment = self.course.get_assignment(5) - edited_assignment = assignment.edit(assignment={'name': name}) + edited_assignment = self.assignment.edit(assignment={'name': name}) assert isinstance(edited_assignment, Assignment) assert hasattr(edited_assignment, 'name') assert edited_assignment.name == name # delete() - def test_delete_assignments(self): - - assignment = self.course.get_assignment('5') + def test_delete_assignments(self, m): + register_uris({'assignment': ['delete_assignment']}, m) - deleted_assignment = assignment.delete() + deleted_assignment = self.assignment.delete() assert isinstance(deleted_assignment, Assignment) # __str__() - def test__str__(self): - string = str(self.course.get_assignment('5')) + def test__str__(self, m): + string = str(self.assignment) assert isinstance(string, str) From 93ca3ec053507fbca8688ae3bd47b7a0a985b7da Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Thu, 15 Dec 2016 14:00:29 -0500 Subject: [PATCH 130/145] Updated test_canvas --- tests/test_canvas.py | 195 ++++++++++++++++++++++++------------ tests/test_canvas_object.py | 3 - 2 files changed, 129 insertions(+), 69 deletions(-) diff --git a/tests/test_canvas.py b/tests/test_canvas.py index ce2cc0b0..02d5dea1 100644 --- a/tests/test_canvas.py +++ b/tests/test_canvas.py @@ -9,48 +9,47 @@ from pycanvas.course import Course, CourseNickname from pycanvas.group import Group from pycanvas.exceptions import ResourceDoesNotExist +from pycanvas.progress import Progress from pycanvas.section import Section from pycanvas.user import User from tests import settings from tests.util import register_uris +@requests_mock.Mocker() class TestCanvas(unittest.TestCase): - """ - Test core Canvas functionality. - """ - @classmethod - def setUpClass(self): - requires = { - 'account': [ - 'create', 'domains', 'get_by_id', 'multiple', 'multiple_course' - ], - 'conversation': [ - 'get_by_id', 'get_conversations', 'get_conversations_2', - 'create_conversation', 'mark_all_as_read', 'unread_count', - 'get_running_batches', 'batch_update' - ], - 'course': [ - 'get_by_id', 'multiple', 'multiple_page_2', 'start_at_date', - 'unicode_encode_error' - ], - 'group': ['canvas_create_group', 'canvas_get_group'], - 'section': ['get_by_id'], - 'user': [ - 'activity_stream_summary', 'course_nickname', 'course_nickname_set', - 'course_nicknames', 'course_nicknames_delete', - 'course_nicknames_page_2', 'courses', 'courses_p2', 'get_by_id', - 'get_by_id_type', 'todo_items', 'upcoming_events' - ] - } - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, {'generic': ['not_found']}, adapter) - register_uris(settings.BASE_URL, requires, adapter) + @classmethod + def setUp(self): + # requires = { + # 'account': [ + # 'create', 'domains', 'get_by_id', 'multiple', 'multiple_course' + # ], + # 'conversation': [ + # 'get_by_id', 'get_conversations', 'get_conversations_2', + # 'create_conversation', 'mark_all_as_read', 'unread_count', + # 'get_running_batches', 'batch_update' + # ], + # 'course': [ + # 'get_by_id', 'multiple', 'multiple_page_2', '2', + # 'unicode_encode_error' + # ], + # 'group': ['canvas_create_group', 'canvas_get_group'], + # 'section': ['get_by_id'], + # 'user': [ + # 'activity_stream_summary', 'course_nickname', 'course_nickname_set', + # 'course_nicknames', 'course_nicknames_delete', + # 'course_nicknames_page_2', 'courses', 'courses_p2', 'get_by_id', + # 'get_by_id_type', 'todo_items', 'upcoming_events' + # ] + # } + + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) # create_account() - def test_create_account(self): + def test_create_account(self, m): + register_uris({'account': ['create']}, m) + name = 'Newly Created Account' account_dict = { @@ -63,35 +62,47 @@ def test_create_account(self): assert account.name == name # get_account() - def test_get_account(self): + def test_get_account(self, m): + register_uris({'account': ['get_by_id']}, m) + account = self.canvas.get_account(1) assert isinstance(account, Account) - def test_get_account_fail(self): + def test_get_account_fail(self, m): + register_uris({'generic': ['not_found']}, m) + with self.assertRaises(ResourceDoesNotExist): self.canvas.get_account(settings.INVALID_ID) # get_accounts() - def test_get_accounts(self): + def test_get_accounts(self, m): + register_uris({'account': ['multiple']}, m) + accounts = self.canvas.get_accounts() account_list = [account for account in accounts] assert len(account_list) == 2 # get_course_accounts() - def test_get_course_accounts(self): + def test_get_course_accounts(self, m): + register_uris({'account': ['multiple_course']}, m) + accounts = self.canvas.get_course_accounts() account_list = [account for account in accounts] assert len(account_list) == 2 # get_course() - def test_get_course(self): + def test_get_course(self, m): + register_uris({'course': ['get_by_id']}, m) + course = self.canvas.get_course(1) assert isinstance(course, Course) assert hasattr(course, 'name') - def test_get_course_with_start_date(self): + def test_get_course_with_start_date(self, m): + register_uris({'course': ['start_at_date']}, m) + course = self.canvas.get_course(2) assert hasattr(course, 'start_at') @@ -99,34 +110,46 @@ def test_get_course_with_start_date(self): assert hasattr(course, 'start_at_date') assert isinstance(course.start_at_date, datetime) - def test_get_course_non_unicode_char(self): + def test_get_course_non_unicode_char(self, m): + register_uris({'course': ['unicode_encode_error']}, m) + course = self.canvas.get_course(3) assert hasattr(course, 'name') - def test_get_course_fail(self): + def test_get_course_fail(self, m): + register_uris({'generic': ['not_found']}, m) + with self.assertRaises(ResourceDoesNotExist): self.canvas.get_course(settings.INVALID_ID) # get_user() - def test_get_user(self): + def test_get_user(self, m): + register_uris({'user': ['get_by_id']}, m) + user = self.canvas.get_user(1) assert isinstance(user, User) assert hasattr(user, 'name') - def test_get_user_by_id_type(self): + def test_get_user_by_id_type(self, m): + register_uris({'user': ['get_by_id_type']}, m) + user = self.canvas.get_user('jdoe', 'sis_user_id') assert isinstance(user, User) assert hasattr(user, 'name') - def test_get_user_fail(self): + def test_get_user_fail(self, m): + register_uris({'generic': ['not_found']}, m) + with self.assertRaises(ResourceDoesNotExist): self.canvas.get_user(settings.INVALID_ID) # get_courses() - def test_get_courses(self): + def test_get_courses(self, m): + register_uris({'course': ['multiple', 'multiple_page_2']}, m) + courses = self.canvas.get_courses(per_page=1) course_list = [course for course in courses] @@ -134,25 +157,33 @@ def test_get_courses(self): assert isinstance(course_list[0], Course) # get_activity_stream_summary() - def test_get_activity_stream_summary(self): + def test_get_activity_stream_summary(self, m): + register_uris({'user': ['activity_stream_summary']}, m) + summary = self.canvas.get_activity_stream_summary() assert isinstance(summary, list) # get_todo_items() - def test_get_todo_items(self): + def test_get_todo_items(self, m): + register_uris({'user': ['todo_items']}, m) + todo_items = self.canvas.get_todo_items() assert isinstance(todo_items, list) # get_upcoming_events() - def test_get_upcoming_events(self): + def test_get_upcoming_events(self, m): + register_uris({'user': ['upcoming_events']}, m) + events = self.canvas.get_upcoming_events() assert isinstance(events, list) # get_course_nicknames() - def test_get_course_nicknames(self): + def test_get_course_nicknames(self, m): + register_uris({'user': ['course_nicknames', 'course_nicknames_page_2']}, m) + nicknames = self.canvas.get_course_nicknames() nickname_list = [name for name in nicknames] @@ -161,18 +192,24 @@ def test_get_course_nicknames(self): assert hasattr(nickname_list[0], 'nickname') # get_course_nickname() - def test_get_course_nickname(self): + def test_get_course_nickname(self, m): + register_uris({'user': ['course_nickname']}, m) + nickname = self.canvas.get_course_nickname(1) assert isinstance(nickname, CourseNickname) assert hasattr(nickname, 'nickname') - def test_get_course_nickname_fail(self): + def test_get_course_nickname_fail(self, m): + register_uris({'generic': ['not_found']}, m) + with self.assertRaises(ResourceDoesNotExist): self.canvas.get_course_nickname(settings.INVALID_ID) # set_course_nickname() - def test_set_course_nickname(self): + def test_set_course_nickname(self, m): + register_uris({'user': ['course_nickname_set']}, m) + name = 'New Course Nickname' nickname = self.canvas.set_course_nickname(1, name) @@ -182,12 +219,16 @@ def test_set_course_nickname(self): assert nickname.nickname == name # clear_course_nicknames() - def test_clear_course_nicknames(self): + def test_clear_course_nicknames(self, m): + register_uris({'user': ['course_nicknames_delete']}, m) + success = self.canvas.clear_course_nicknames() assert success # search_accounts() - def test_search_accounts(self): + def test_search_accounts(self, m): + register_uris({'account': ['domains']}, m) + domains = self.canvas.search_accounts() assert isinstance(domains, list) @@ -195,13 +236,17 @@ def test_search_accounts(self): assert 'name' in domains[0] # get_section() - def test_section(self): + def test_get_section(self, m): + register_uris({'section': ['get_by_id']}, m) + info = self.canvas.get_section(1) assert isinstance(info, Section) # create_group() - def test_create_group(self): + def test_create_group(self, m): + register_uris({'group': ['canvas_create_group']}, m) + group = self.canvas.create_group() assert isinstance(group, Group) @@ -209,7 +254,9 @@ def test_create_group(self): assert hasattr(group, 'description') # get_group() - def test_get_group(self): + def test_get_group(self, m): + register_uris({'group': ['canvas_get_group']}, m) + group = self.canvas.get_group(1) assert isinstance(group, Group) @@ -217,7 +264,9 @@ def test_get_group(self): assert hasattr(group, 'description') # create_conversation() - def test_create_conversation(self): + def test_create_conversation(self, m): + register_uris({'conversation': ['create_conversation']}, m) + recipients = ['1', '2'] body = 'Test Conversation Body' @@ -228,14 +277,21 @@ def test_create_conversation(self): assert len(conversation_list) == 2 # get_conversation() - def test_get_conversation(self): + def test_get_conversation(self, m): + register_uris({'conversation': ['get_by_id']}, m) + convo = self.canvas.get_conversation(1) assert isinstance(convo, Conversation) assert hasattr(convo, 'subject') # get_conversations() - def test_get_conversations(self): + def test_get_conversations(self, m): + requires = { + 'conversation': ['get_conversations', 'get_conversations_2'] + } + register_uris(requires, m) + convos = self.canvas.get_conversations() conversation_list = [conversation for conversation in convos] @@ -243,25 +299,32 @@ def test_get_conversations(self): assert isinstance(conversation_list[0], Conversation) # mark_all_as_read() - def test_conversations_mark_all_as_read(self): + def test_conversations_mark_all_as_read(self, m): + register_uris({'conversation': ['mark_all_as_read']}, m) + result = self.canvas.conversations_mark_all_as_read() assert result is True # unread_count() - def test_conversations_unread_count(self): + def test_conversations_unread_count(self, m): + register_uris({'conversation': ['unread_count']}, m) + result = self.canvas.conversations_unread_count() assert result['unread_count'] == "7" # get_running_batches() - def test_conversations_get_running_batches(self): + def test_conversations_get_running_batches(self, m): + register_uris({'conversation': ['get_running_batches']}, m) + result = self.canvas.conversations_get_running_batches() assert len(result) == 2 assert 'body' in result[0]['message'] assert result[1]['message']['author_id'] == 1 # batch_update() - def test_conversations_batch_update(self): - from pycanvas.progress import Progress + def test_conversations_batch_update(self, m): + register_uris({'conversation': ['batch_update']}, m) + conversation_ids = [1, 2] this_event = "mark_as_read" result = self.canvas.conversations_batch_update( @@ -270,7 +333,7 @@ def test_conversations_batch_update(self): ) assert isinstance(result, Progress) - def test_conversations_batch_updated_fail_on_event(self): + 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( @@ -279,7 +342,7 @@ def test_conversations_batch_updated_fail_on_event(self): ) assert isinstance(result, ValueError) - def test_conversations_batch_updated_fail_on_ids(self): + 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( diff --git a/tests/test_canvas_object.py b/tests/test_canvas_object.py index 058b497a..1bf0e8c6 100644 --- a/tests/test_canvas_object.py +++ b/tests/test_canvas_object.py @@ -4,9 +4,6 @@ class TestCanvasObject(unittest.TestCase): - """ - Test CanvasObject functionality. - """ # to_json() def test_canvas_object_to_json(self): From b48927725e2a5dce66493303a6e3a0a1653dc8f5 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Thu, 15 Dec 2016 16:37:39 -0500 Subject: [PATCH 131/145] Updated test_conversation --- tests/test_canvas.py | 23 --------------- tests/test_conversation.py | 59 +++++++++++++++++++++----------------- 2 files changed, 33 insertions(+), 49 deletions(-) diff --git a/tests/test_canvas.py b/tests/test_canvas.py index 02d5dea1..2c27ed52 100644 --- a/tests/test_canvas.py +++ b/tests/test_canvas.py @@ -21,29 +21,6 @@ class TestCanvas(unittest.TestCase): @classmethod def setUp(self): - # requires = { - # 'account': [ - # 'create', 'domains', 'get_by_id', 'multiple', 'multiple_course' - # ], - # 'conversation': [ - # 'get_by_id', 'get_conversations', 'get_conversations_2', - # 'create_conversation', 'mark_all_as_read', 'unread_count', - # 'get_running_batches', 'batch_update' - # ], - # 'course': [ - # 'get_by_id', 'multiple', 'multiple_page_2', '2', - # 'unicode_encode_error' - # ], - # 'group': ['canvas_create_group', 'canvas_get_group'], - # 'section': ['get_by_id'], - # 'user': [ - # 'activity_stream_summary', 'course_nickname', 'course_nickname_set', - # 'course_nicknames', 'course_nicknames_delete', - # 'course_nicknames_page_2', 'courses', 'courses_p2', 'get_by_id', - # 'get_by_id_type', 'todo_items', 'upcoming_events' - # ] - # } - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) # create_account() diff --git a/tests/test_conversation.py b/tests/test_conversation.py index c80cee10..6d18b97e 100644 --- a/tests/test_conversation.py +++ b/tests/test_conversation.py @@ -8,53 +8,56 @@ from tests.util import register_uris +@requests_mock.Mocker() class TestConversation(unittest.TestCase): - """ - Tests Conversation functionality. - """ + @classmethod - def setUpClass(self): - requires = { - 'conversation': [ - 'add_message', 'add_recipients', 'delete_conversation', - 'delete_conversation_fail', 'delete_message', 'edit_conversation', - 'edit_conversation_fail', 'get_by_id', 'get_by_id_2' - ] - } - - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, {'generic': ['not_found']}, adapter) - register_uris(settings.BASE_URL, requires, adapter) - - self.conversation = self.canvas.get_conversation(1) + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + register_uris({'conversation': ['get_by_id']}, m) + + self.conversation = self.canvas.get_conversation(1) # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.conversation) assert isinstance(string, str) # edit() - def test_edit(self): + def test_edit(self, m): + register_uris({'conversation': ['edit_conversation']}, m) + new_subject = "conversations api example" success = self.conversation.edit(subject=new_subject) assert success - def test_edit_fail(self): + def test_edit_fail(self, m): + requires = {'conversation': ['get_by_id_2', 'edit_conversation_fail']} + register_uris(requires, m) + temp_convo = self.canvas.get_conversation(2) assert temp_convo.edit() is False # delete() - def test_delete(self): + def test_delete(self, m): + register_uris({'conversation': ['delete_conversation']}, m) + success = self.conversation.delete() assert success - def test_delete_fail(self): + def test_delete_fail(self, m): + requires = {'conversation': ['get_by_id_2', 'delete_conversation_fail']} + register_uris(requires, m) + temp_convo = self.canvas.get_conversation(2) assert temp_convo.delete() is False # add_recipients() - def test_add_recipients(self): + def test_add_recipients(self, m): + register_uris({'conversation': ['add_recipients']}, m) + recipients = {'bob': 1, 'joe': 2} string_bob = "Bob was added to the conversation by Hank TA" string_joe = "Joe was added to the conversation by Hank TA" @@ -65,7 +68,9 @@ def test_add_recipients(self): assert result.messages[1]["body"] == string_joe # add_message() - def test_add_message(self): + def test_add_message(self, m): + register_uris({'conversation': ['add_message']}, m) + test_string = "add_message test body" result = self.conversation.add_message(test_string) assert isinstance(result, Conversation) @@ -73,7 +78,9 @@ def test_add_message(self): assert result.messages[0]['id'] == 3 # delete_message() - def test_delete_message(self): + def test_delete_message(self, m): + register_uris({'conversation': ['delete_message']}, m) + id_list = [1] result = self.conversation.delete_messages(id_list) assert 'subject' in result From 51b8391f8b117aa996d17ec2c428d5a9dd931da2 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Mon, 19 Dec 2016 16:45:14 -0500 Subject: [PATCH 132/145] corrected a mislabled fixture in course.json. Updated test_course --- tests/fixtures/course.json | 18 ++- tests/test_course.py | 262 +++++++++++++++++++++---------------- 2 files changed, 168 insertions(+), 112 deletions(-) diff --git a/tests/fixtures/course.json b/tests/fixtures/course.json index 5f7b7cd7..2595ba85 100644 --- a/tests/fixtures/course.json +++ b/tests/fixtures/course.json @@ -1,5 +1,13 @@ { - "create": { + "conclude": { + "method": "DELETE", + "endpoint": "courses/1", + "data": { + "conclude": true + }, + "status_code": 200 + }, + "create_quiz": { "method": "POST", "endpoint": "courses/1/quizzes", "data": { @@ -19,6 +27,14 @@ }, "status_code": 200 }, + "delete": { + "method": "DELETE", + "endpoint": "courses/1", + "data": { + "delete": true + }, + "status_code": 200 + }, "enroll_user": { "method": "POST", "endpoint": "courses/1/enrollments", diff --git a/tests/test_course.py b/tests/test_course.py index b5e000d9..79e4f0a5 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -1,6 +1,5 @@ import unittest import uuid -import requests import os import requests_mock @@ -20,98 +19,74 @@ from tests.util import register_uris +@requests_mock.Mocker() class TestCourse(unittest.TestCase): @classmethod - def setUpClass(self): - requires = { - 'course': [ - 'create', 'create_assignment', 'create_section', 'create_module', - 'create_page', 'edit_front_page', 'enroll_user', - 'get_all_assignments', 'get_all_assignments2', - 'get_assignment_by_id', 'get_by_id', 'get_external_tools', - 'get_external_tools_p2', 'get_module_by_id', 'get_page', - 'get_pages', 'get_pages2', 'get_quiz', 'get_recent_students', - 'get_recent_students_p2', 'get_section', 'get_user', - 'get_user_id_type', 'get_users', 'get_users_p2', - 'list_enrollments', 'list_enrollments_2', 'list_modules', 'list_groups_context', - 'list_modules2', 'list_sections', 'list_sections2', 'list_quizzes', 'list_quizzes2', - 'list_groups_context2', 'preview_html', 'reset', 'settings', - 'show_front_page', 'update', 'update_settings', 'upload', - 'upload_final', 'create_group_category', 'list_group_categories' - ], - 'external_tool': ['get_by_id_course'], - 'quiz': ['get_by_id'], - 'user': ['get_by_id'], - } - - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, {'generic': ['not_found']}, adapter) - register_uris(settings.BASE_URL, requires, adapter) - - # define custom matchers - def conclude_matcher(request): - if (request.path_url == '/api/v1/courses/1' and request.body and - 'event=conclude' in request.body): - resp = requests.Response() - resp.status_code = 200 - resp._content = '{"conclude": true}' - return resp - - def delete_matcher(request): - if (request.path_url == '/api/v1/courses/1' and request.body and - 'event=delete' in request.body): - resp = requests.Response() - resp.status_code = 200 - resp._content = '{"delete": true}' - return resp - - # register custom matchers - adapter.add_matcher(conclude_matcher) - adapter.add_matcher(delete_matcher) - - self.course = self.canvas.get_course(1) - self.page = self.course.get_page('my-url') - self.quiz = self.course.get_quiz(1) - self.user = self.canvas.get_user(1) + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + requires = { + 'course': ['get_by_id', 'get_page'], + 'quiz': ['get_by_id'], + 'user': ['get_by_id'] + } + register_uris(requires, m) + + self.course = self.canvas.get_course(1) + self.page = self.course.get_page('my-url') + self.quiz = self.course.get_quiz(1) + self.user = self.canvas.get_user(1) # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.course) assert isinstance(string, str) # conclude() - def test_conclude(self): + def test_conclude(self, m): + register_uris({'course': ['conclude']}, m) + success = self.course.conclude() assert success # delete() - def test_delete(self): + def test_delete(self, m): + register_uris({'course': ['delete']}, m) + success = self.course.delete() assert success # update() - def test_update(self): + def test_update(self, m): + register_uris({'course': ['update']}, m) + new_name = 'New Name' self.course.update(course={'name': new_name}) assert self.course.name == new_name # get_user() - def test_get_user(self): + def test_get_user(self, m): + register_uris({'course': ['get_user']}, m) + user = self.course.get_user(1) assert isinstance(user, User) assert hasattr(user, 'name') - def test_get_user_id_type(self): + def test_get_user_id_type(self, m): + register_uris({'course': ['get_user_id_type']}, m) + user = self.course.get_user("SISLOGIN", "sis_login_id") assert isinstance(user, User) assert hasattr(user, 'name') # get_users() - def test_get_users(self): + def test_get_users(self, m): + register_uris({'course': ['get_users', 'get_users_p2']}, m) + users = self.course.get_users() user_list = [user for user in users] @@ -119,7 +94,13 @@ def test_get_users(self): assert isinstance(user_list[0], User) # enroll_user() - def test_enroll_user(self): + def test_enroll_user(self, m): + requires = { + 'course': ['enroll_user'], + 'user': ['get_by_id'] + } + register_uris(requires, m) + enrollment_type = 'TeacherEnrollment' user = self.canvas.get_user(1) enrollment = self.course.enroll_user(user, enrollment_type) @@ -129,7 +110,10 @@ def test_enroll_user(self): assert enrollment.type == enrollment_type # get_recent_students() - def test_get_recent_students(self): + def test_get_recent_students(self, m): + recent = {'course': ['get_recent_students', 'get_recent_students_p2']} + register_uris(recent, m) + students = self.course.get_recent_students() student_list = [student for student in students] @@ -138,7 +122,9 @@ def test_get_recent_students(self): assert hasattr(student_list[0], 'name') # preview_html() - def test_preview_html(self): + def test_preview_html(self, m): + register_uris({'course': ['preview_html']}, m) + html_str = "

hello

" prev_html = self.course.preview_html(html_str) @@ -146,20 +132,26 @@ def test_preview_html(self): assert prev_html == "

hello

" # get_settings() - def test_get_settings(self): + def test_get_settings(self, m): + register_uris({'course': ['settings']}, m) + settings = self.course.get_settings() assert isinstance(settings, dict) # update_settings() - def test_update_settings(self): + def test_update_settings(self, m): + register_uris({'course': ['update_settings']}, m) + settings = self.course.update_settings() assert isinstance(settings, dict) assert settings['hide_final_grades'] is True # upload() - def test_upload(self): + def test_upload(self, m): + register_uris({'course': ['upload', 'upload_final']}, m) + filename = 'testfile_%s' % uuid.uuid4().hex file = open(filename, 'w+') @@ -177,14 +169,18 @@ def test_upload(self): pass # reset() - def test_reset(self): + def test_reset(self, m): + register_uris({'course': ['reset']}, m) + course = self.course.reset() assert isinstance(course, Course) assert hasattr(course, 'name') # create_quiz() - def test_create_quiz(self): + def test_create_quiz(self, m): + register_uris({'course': ['create_quiz']}, m) + title = 'Newer Title' new_quiz = self.course.create_quiz({'title': title}) @@ -194,24 +190,30 @@ def test_create_quiz(self): assert hasattr(new_quiz, 'course_id') assert new_quiz.course_id == self.course.id - def test_create_quiz_fail(self): + def test_create_quiz_fail(self, m): with self.assertRaises(RequiredFieldMissing): self.course.create_quiz({}) # get_quiz() - def test_get_quiz(self): + def test_get_quiz(self, m): + register_uris({'course': ['get_quiz']}, m) + target_quiz = self.course.get_quiz(1) assert isinstance(target_quiz, Quiz) assert hasattr(target_quiz, 'course_id') assert target_quiz.course_id == self.course.id - def test_get_quiz_fail(self): + def test_get_quiz_fail(self, m): + register_uris({'generic': ['not_found']}, m) + with self.assertRaises(ResourceDoesNotExist): self.course.get_quiz(settings.INVALID_ID) # get_quizzes() - def test_get_quizzes(self): + def test_get_quizzes(self, m): + register_uris({'course': ['list_quizzes', 'list_quizzes2']}, m) + quizzes = self.course.get_quizzes() quiz_list = [quiz for quiz in quizzes] @@ -221,7 +223,9 @@ def test_get_quizzes(self): assert quiz_list[0].course_id == self.course.id # get_modules() - def test_get_modules(self): + def test_get_modules(self, m): + register_uris({'course': ['list_modules', 'list_modules2']}, m) + modules = self.course.get_modules() module_list = [module for module in modules] @@ -231,7 +235,9 @@ def test_get_modules(self): assert module_list[0].course_id == self.course.id # get_module() - def test_get_module(self): + def test_get_module(self, m): + register_uris({'course': ['get_module_by_id']}, m) + target_module = self.course.get_module(1) assert isinstance(target_module, Module) @@ -239,7 +245,9 @@ def test_get_module(self): assert target_module.course_id == self.course.id # create_module() - def test_create_module(self): + def test_create_module(self, m): + register_uris({'course': ['create_module']}, m) + name = 'Name' new_module = self.course.create_module(module={'name': name}) @@ -248,12 +256,14 @@ def test_create_module(self): assert hasattr(new_module, 'course_id') assert new_module.course_id == self.course.id - def test_create_module_fail(self): + def test_create_module_fail(self, m): with self.assertRaises(RequiredFieldMissing): self.course.create_module(module={}) # get_enrollments() - def test_get_enrollments(self): + def test_get_enrollments(self, m): + register_uris({'course': ['list_enrollments', 'list_enrollments_2']}, m) + enrollments = self.course.get_enrollments() enrollment_list = [enrollment for enrollment in enrollments] @@ -261,13 +271,17 @@ def test_get_enrollments(self): assert isinstance(enrollment_list[0], Enrollment) # get_section - def test_get_section(self): + def test_get_section(self, m): + register_uris({'course': ['get_section']}, m) + section = self.course.get_section(1) assert isinstance(section, Section) # create_assignment() - def test_create_assignment(self): + def test_create_assignment(self, m): + register_uris({'course': ['create_assignment']}, m) + name = 'Newly Created Assignment' assignment = self.course.create_assignment(assignment={'name': name}) @@ -277,19 +291,24 @@ def test_create_assignment(self): assert assignment.name == name assert assignment.id == 5 - def test_create_assignment_fail(self): + def test_create_assignment_fail(self, m): with self.assertRaises(RequiredFieldMissing): self.course.create_assignment(assignment={}) # get_assignment() - def test_get_assignment(self): + def test_get_assignment(self, m): + register_uris({'course': ['get_assignment_by_id']}, m) + assignment = self.course.get_assignment('5') assert isinstance(assignment, Assignment) assert hasattr(assignment, 'name') # get_assignments() - def test_get_assignments(self): + def test_get_assignments(self, m): + requires = {'course': ['get_all_assignments', 'get_all_assignments2']} + register_uris(requires, m) + assignments = self.course.get_assignments() assignment_list = [assignment for assignment in assignments] @@ -297,7 +316,9 @@ def test_get_assignments(self): assert len(assignment_list) == 4 # show_front_page() - def test_show_front_page(self): + def test_show_front_page(self, m): + register_uris({'course': ['show_front_page']}, m) + front_page = self.course.show_front_page() assert isinstance(front_page, Page) @@ -305,7 +326,9 @@ def test_show_front_page(self): assert hasattr(front_page, 'title') # create_front_page() - def test_edit_front_page(self): + def test_edit_front_page(self, m): + register_uris({'course': ['edit_front_page']}, m) + new_front_page = self.course.edit_front_page() assert isinstance(new_front_page, Page) @@ -313,14 +336,18 @@ def test_edit_front_page(self): assert hasattr(new_front_page, 'title') # get_page() - def test_get_page(self): + def test_get_page(self, m): + register_uris({'course': ['get_page']}, m) + url = 'my-url' page = self.course.get_page(url) assert isinstance(page, Page) # get_pages() - def test_get_pages(self): + def test_get_pages(self, m): + register_uris({'course': ['get_pages', 'get_pages2']}, m) + pages = self.course.get_pages() page_list = [page for page in pages] @@ -330,7 +357,9 @@ def test_get_pages(self): assert page_list[0].course_id == self.course.id # create_page() - def test_create_page(self): + def test_create_page(self, m): + register_uris({'course': ['create_page']}, m) + title = "Newest Page" new_page = self.course.create_page(wiki_page={'title': title}) @@ -340,38 +369,50 @@ def test_create_page(self): assert hasattr(new_page, 'course_id') assert new_page.course_id == self.course.id - def test_create_page_fail(self): + def test_create_page_fail(self, m): with self.assertRaises(RequiredFieldMissing): self.course.create_page(settings.INVALID_ID) # get_external_tool() - def test_get_external_tool(self): + def test_get_external_tool(self, m): + register_uris({'external_tool': ['get_by_id_course']}, m) + tool = self.course.get_external_tool(1) assert isinstance(tool, ExternalTool) assert hasattr(tool, 'name') # get_external_tools() - def test_get_external_tools(self): + def test_get_external_tools(self, m): + requires = {'course': ['get_external_tools', 'get_external_tools_p2']} + register_uris(requires, m) + tools = self.course.get_external_tools() tool_list = [tool for tool in tools] assert isinstance(tool_list[0], ExternalTool) assert len(tool_list) == 4 - def test_list_sections(self): + def test_list_sections(self, m): + register_uris({'course': ['list_sections', 'list_sections2']}, m) + sections = self.course.list_sections() section_list = [sect for sect in sections] assert isinstance(section_list[0], Section) assert len(section_list) == 4 - def test_create_course_section(self): + def test_create_course_section(self, m): + register_uris({'course': ['create_section']}, m) + section = self.course.create_course_section() assert isinstance(section, Section) - def test_list_groups(self): + def test_list_groups(self, m): + requires = {'course': ['list_groups_context', 'list_groups_context2']} + register_uris(requires, m) + groups = self.course.list_groups() group_list = [group for group in groups] @@ -379,43 +420,42 @@ def test_list_groups(self): assert len(group_list) == 4 # create_group_category() - def test_create_group_category(self): + def test_create_group_category(self, m): + register_uris({'course': ['create_group_category']}, m) + name_str = "Test String" response = self.course.create_group_category(name=name_str) assert isinstance(response, GroupCategory) # list_group_categories() - def test_list_group_categories(self): + def test_list_group_categories(self, m): + register_uris({'course': ['list_group_categories']}, m) + response = self.course.list_group_categories() category_list = [category for category in response] assert isinstance(category_list[0], GroupCategory) +@requests_mock.Mocker() class TestCourseNickname(unittest.TestCase): - """ - Tests CourseNickname methods - """ - @classmethod - def setUpClass(self): - requires = { - 'course': [], - 'generic': ['not_found'], - 'user': ['course_nickname', 'remove_nickname'] - } - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) + @classmethod + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) - self.nickname = self.canvas.get_course_nickname(1) + with requests_mock.Mocker() as m: + register_uris({'user': ['course_nickname']}, m) + self.nickname = self.canvas.get_course_nickname(1) # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.nickname) assert isinstance(string, str) # remove() - def test_remove(self): + def test_remove(self, m): + register_uris({'user': ['remove_nickname']}, m) + deleted_nick = self.nickname.remove() assert isinstance(deleted_nick, CourseNickname) From fe13e79cdeb44162e6f64c8f1e4389ab5906d03a Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Tue, 20 Dec 2016 09:02:00 -0500 Subject: [PATCH 133/145] Updated test_enrollment and test_external_tool --- tests/test_enrollment.py | 41 +++++++++++---------- tests/test_external_tool.py | 73 +++++++++++++++++++++---------------- 2 files changed, 63 insertions(+), 51 deletions(-) diff --git a/tests/test_enrollment.py b/tests/test_enrollment.py index ab41a560..635600cc 100644 --- a/tests/test_enrollment.py +++ b/tests/test_enrollment.py @@ -8,41 +8,44 @@ from tests.util import register_uris +@requests_mock.Mocker() class TestEnrollment(unittest.TestCase): - """ - Test Enrollment methods - """ + @classmethod - def setUpClass(self): - requires = { - 'account': ['get_by_id'], - 'generic': ['not_found'], - 'enrollment': ['deactivate', 'get_by_id', 'reactivate'] - } - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) - - self.account = self.canvas.get_account(1) - self.enrollment = self.account.get_enrollment(1) + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + requires = { + 'account': ['get_by_id'], + 'enrollment': ['get_by_id'] + } + register_uris(requires, m) + + self.account = self.canvas.get_account(1) + self.enrollment = self.account.get_enrollment(1) # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.enrollment) self.assertIsInstance(string, str) # deactivate() - def test_deactivate(self): + def test_deactivate(self, m): + register_uris({'enrollment': ['deactivate']}, m) + target_enrollment = self.enrollment.deactivate('conclude') self.assertIsInstance(target_enrollment, Enrollment) - def test_deactivate_invalid_task(self): + def test_deactivate_invalid_task(self, m): with self.assertRaises(ValueError): self.enrollment.deactivate('finish') # reactivate() - def test_reactivate(self): + def test_reactivate(self, m): + register_uris({'enrollment': ['reactivate']}, m) + target_enrollment = self.enrollment.reactivate() self.assertIsInstance(target_enrollment, Enrollment) diff --git a/tests/test_external_tool.py b/tests/test_external_tool.py index 7f73c72d..a9060178 100644 --- a/tests/test_external_tool.py +++ b/tests/test_external_tool.py @@ -11,71 +11,80 @@ from tests.util import register_uris +@requests_mock.Mocker() class TestExternalTool(unittest.TestCase): - """ - Tests Courses functionality - """ - @classmethod - def setUpClass(self): - requires = { - 'account': ['get_by_id'], - 'course': ['get_by_id', 'get_by_id_2'], - 'external_tool': [ - 'get_by_id_account', 'get_by_id_course', 'get_by_id_course_2', - 'get_sessionless_launch_url_course', 'sessionless_launch_no_url' - ], - } - - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) - self.course = self.canvas.get_course(1) - self.account = self.canvas.get_account(1) - self.ext_tool_course = self.course.get_external_tool(1) - self.ext_tool_account = self.account.get_external_tool(1) + @classmethod + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + requires = { + 'account': ['get_by_id'], + 'course': ['get_by_id'], + 'external_tool': ['get_by_id_account', 'get_by_id_course'], + } + register_uris(requires, m) + + self.course = self.canvas.get_course(1) + self.account = self.canvas.get_account(1) + self.ext_tool_course = self.course.get_external_tool(1) + self.ext_tool_account = self.account.get_external_tool(1) # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.ext_tool_course) assert isinstance(string, str) # parent_id - def test_parent_id_account(self): + def test_parent_id_account(self, m): assert self.ext_tool_account.parent_id == 1 - def test_parent_id_course(self): + def test_parent_id_course(self, m): assert self.ext_tool_course.parent_id == 1 - def test_parent_id_no_id(self): + def test_parent_id_no_id(self, m): tool = ExternalTool(self.canvas._Canvas__requester, {'id': 1}) with self.assertRaises(ValueError): tool.parent_id # parent_type - def test_parent_type_account(self): + def test_parent_type_account(self, m): assert self.ext_tool_account.parent_type == 'account' - def test_parent_type_course(self): + def test_parent_type_course(self, m): assert self.ext_tool_course.parent_type == 'course' - def test_parent_type_no_id(self): + def test_parent_type_no_id(self, m): tool = ExternalTool(self.canvas._Canvas__requester, {'id': 1}) with self.assertRaises(ValueError): tool.parent_type # get_parent() - def test_get_parent_account(self): + def test_get_parent_account(self, m): + register_uris({'account': ['get_by_id']}, m) assert isinstance(self.ext_tool_account.get_parent(), Account) - def test_get_parent_course(self): + def test_get_parent_course(self, m): + register_uris({'course': ['get_by_id']}, m) assert isinstance(self.ext_tool_course.get_parent(), Course) # get_sessionless_launch_url() - def test_get_sessionless_launch_url(self): + def test_get_sessionless_launch_url(self, m): + requires = {'external_tool': ['get_sessionless_launch_url_course']} + register_uris(requires, m) + assert isinstance(self.ext_tool_course.get_sessionless_launch_url(), (str, unicode)) - def test_get_sessionless_launch_url_no_url(self): + def test_get_sessionless_launch_url_no_url(self, m): + requires = { + 'course': ['get_by_id_2'], + 'external_tool': [ + 'get_by_id_course_2', 'sessionless_launch_no_url' + ] + } + register_uris(requires, m) + course = self.canvas.get_course(2) ext_tool = course.get_external_tool(2) with self.assertRaises(CanvasException): From 5eecae95b03cc31eb9c656cf9376313920edcebd Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Tue, 20 Dec 2016 10:41:06 -0500 Subject: [PATCH 134/145] Moved get group category method to canvas.py. Updated test_group --- pycanvas/canvas.py | 17 ++- pycanvas/group.py | 15 --- tests/fixtures/group.json | 89 +++++--------- tests/test_canvas.py | 13 +- tests/test_group.py | 252 ++++++++++++++++++-------------------- 5 files changed, 172 insertions(+), 214 deletions(-) diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index 51284d05..66b0a5ce 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -1,6 +1,6 @@ from pycanvas.account import Account from pycanvas.course import Course -from pycanvas.group import Group +from pycanvas.group import Group, GroupCategory from pycanvas.paginated_list import PaginatedList from pycanvas.requester import Requester from pycanvas.user import User @@ -349,6 +349,21 @@ def get_group(self, group_id, **kwargs): ) return Group(self.__requester, response.json()) + def get_group_category(self, cat_id): + """ + Get a single group category. + + :calls: `GET /api/v1/group_categories/:group_category_id \ + `_ + + :rtype: :class:`pycanvas.group.GroupCategory` + """ + response = self.__requester.request( + 'GET', + 'group_categories/%s' % (cat_id) + ) + return GroupCategory(self.__requester, response.json()) + def create_conversation(self, recipients, body, **kwargs): """ Create a new Conversation. diff --git a/pycanvas/group.py b/pycanvas/group.py index 0fdd33b2..112ab7d5 100644 --- a/pycanvas/group.py +++ b/pycanvas/group.py @@ -432,21 +432,6 @@ def create_group(self, **kwargs): ) return Group(self._requester, response.json()) - def get_category(self, cat_id): - """ - Get a single group category. - - :calls: `GET /api/v1/group_categories/:group_category_id \ - `_ - - :rtype: :class:`pycanvas.group.GroupCategory` - """ - response = self._requester.request( - 'GET', - 'group_categories/%s' % (cat_id) - ) - return GroupCategory(self._requester, response.json()) - def update(self, **kwargs): """ Update a group category. diff --git a/tests/fixtures/group.json b/tests/fixtures/group.json index e387f65a..7a76eb31 100644 --- a/tests/fixtures/group.json +++ b/tests/fixtures/group.json @@ -1,5 +1,5 @@ { - "canvas_create_group": { + "create": { "method": "POST", "endpoint": "groups", "data": { @@ -9,17 +9,7 @@ }, "status_code": 200 }, - "canvas_get_group": { - "method": "GET", - "endpoint": "groups/1", - "data": { - "id": 1, - "name": "group1", - "description": "best group ever" - }, - "status_code": 200 - }, - "pages_get_group": { + "get_by_id": { "method": "GET", "endpoint": "groups/1", "data": { @@ -39,7 +29,7 @@ }, "status_code": 200 }, - "group_create_page": { + "create_page": { "method": "POST", "endpoint": "groups/1/pages", "data": { @@ -49,7 +39,7 @@ }, "status_code": 200 }, - "group_edit_front_page": { + "edit_front_page": { "method": "PUT", "endpoint": "groups/1/front_page", "data": { @@ -59,7 +49,7 @@ }, "status_code": 200 }, - "group_show_front_page": { + "show_front_page": { "method": "GET", "endpoint": "groups/1/front_page", "data":{ @@ -69,7 +59,7 @@ }, "status_code": 200 }, - "group_get_page": { + "get_page": { "method": "GET", "endpoint": "groups/1/pages/my-url", "data": { @@ -79,7 +69,7 @@ }, "status_code": 200 }, - "group_get_pages": { + "get_pages": { "method": "GET", "endpoint": "groups/1/pages", "data": [ @@ -99,7 +89,7 @@ "Link": "; rel=\"next\"" } }, - "group_get_pages2": { + "get_pages2": { "method": "GET", "endpoint": "groups/1/get_pages?page=2&per_page=2", "data": [ @@ -116,7 +106,7 @@ ], "status_code": 200 }, - "group_edit": { + "edit": { "method": "PUT", "endpoint": "groups/1", "data": { @@ -126,7 +116,7 @@ }, "status_code": 200 }, - "group_delete": { + "delete": { "method": "DELETE", "endpoint": "groups/1", "data": { @@ -136,7 +126,7 @@ }, "status_code": 200 }, - "group_invite": { + "invite": { "method": "POST", "endpoint": "groups/1/invite", "data": [ @@ -159,7 +149,7 @@ ], "status_code": 200 }, - "group_list_users": { + "list_users": { "method": "GET", "endpoint": "groups/1/users", "data": [ @@ -177,7 +167,7 @@ }, "status_code": 200 }, - "group_list_users_p2": { + "list_users_p2": { "method": "GET", "endpoint": "groups/1/users?page=2&per_page=2", "data": [ @@ -192,16 +182,7 @@ ], "status_code": 200 }, - "group_remove_user": { - "method": "DELETE", - "endpoint": "groups/1/users/1", - "data": { - "id": 1, - "name": "Benjamin Test" - }, - "status_code": 200 - }, - "group_upload": { + "upload": { "method": "POST", "endpoint": "groups/1/files", "data": { @@ -212,14 +193,14 @@ } } }, - "group_upload_final": { + "upload_final": { "method": "POST", "endpoint": "files/upload_response_upload_url", "data": { "url": "great_url_success" } }, - "group_preview_processed_html": { + "preview_processed_html": { "method": "POST", "endpoint": "groups/1/preview_html", "data": { @@ -227,15 +208,7 @@ }, "status_code": 200 }, - "group_activity_stream": { - "method": "", - "endpoint": "", - "data": { - "info": "not yet implemented" - }, - "status_code": 404 - }, - "group_get_activity_stream_summary": { + "activity_stream_summary": { "method": "GET", "endpoint": "groups/1/activity_stream/summary", "data": [ @@ -252,7 +225,7 @@ ], "status_code": 200 }, - "group_list_memberships": { + "list_memberships": { "method": "GET", "endpoint": "groups/1/memberships", "data": [ @@ -280,7 +253,7 @@ }, "status_code": 200 }, - "group_list_memberships_p2": { + "list_memberships_p2": { "method": "GET", "endpoint": "groups/1/memberships?page=2&per_page=2", "data": [ @@ -305,7 +278,7 @@ ], "status_code": 200 }, - "group_get_membership": { + "get_membership": { "method": "GET", "endpoint": "groups/1/users/1", "data": { @@ -319,7 +292,7 @@ }, "status_code": 200 }, - "group_create_membership": { + "create_membership": { "method": "POST", "endpoint": "groups/1/memberships", "data": { @@ -333,7 +306,7 @@ }, "status_code": 200 }, - "group_update_membership": { + "update_membership_user": { "method": "PUT", "endpoint": "groups/1/users/1", "data": { @@ -347,7 +320,7 @@ }, "status_code": 200 }, - "membership_update": { + "update_membership_membership": { "method": "PUT", "endpoint": "groups/1/memberships/1", "data": { @@ -361,24 +334,18 @@ }, "status_code": 200 }, - "membership_remove_user": { + "remove_user": { "method": "DELETE", "endpoint": "groups/1/users/1", - "data": { - - }, + "data": {}, "status_code": 200 }, - "membership_remove_self": { + "remove_self": { "method": "DELETE", "endpoint": "groups/1/memberships/self", - "data": { - - }, + "data": {}, "status_code": 200 }, - - "category_create_group": { "method": "POST", "endpoint": "group_categories/1/groups", @@ -402,7 +369,7 @@ }, "status_code": 200 }, - "category_get_category": { + "get_category_by_id": { "method": "GET", "endpoint": "group_categories/1", "data": { diff --git a/tests/test_canvas.py b/tests/test_canvas.py index 2c27ed52..3d673768 100644 --- a/tests/test_canvas.py +++ b/tests/test_canvas.py @@ -7,7 +7,7 @@ from pycanvas.account import Account from pycanvas.conversation import Conversation from pycanvas.course import Course, CourseNickname -from pycanvas.group import Group +from pycanvas.group import Group, GroupCategory from pycanvas.exceptions import ResourceDoesNotExist from pycanvas.progress import Progress from pycanvas.section import Section @@ -222,7 +222,7 @@ def test_get_section(self, m): # create_group() def test_create_group(self, m): - register_uris({'group': ['canvas_create_group']}, m) + register_uris({'group': ['create']}, m) group = self.canvas.create_group() @@ -232,7 +232,7 @@ def test_create_group(self, m): # get_group() def test_get_group(self, m): - register_uris({'group': ['canvas_get_group']}, m) + register_uris({'group': ['get_by_id']}, m) group = self.canvas.get_group(1) @@ -240,6 +240,13 @@ def test_get_group(self, m): assert hasattr(group, 'name') assert hasattr(group, 'description') + # get_group_category() + def test_get_group_category(self, m): + register_uris({'group': ['get_category_by_id']}, m) + + response = self.canvas.get_group_category(1) + assert isinstance(response, GroupCategory) + # create_conversation() def test_create_conversation(self, m): register_uris({'conversation': ['create_conversation']}, m) diff --git a/tests/test_group.py b/tests/test_group.py index 38db35bb..433895e3 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -1,4 +1,6 @@ +import os import unittest +import uuid import requests_mock @@ -10,110 +12,100 @@ from tests.util import register_uris +@requests_mock.Mocker() class TestGroup(unittest.TestCase): - """ - Tests Group functionality - """ - @classmethod - def setUpClass(self): - requires = { - 'course': ['get_by_id', 'show_front_page'], - 'group': [ - 'canvas_create_group', 'canvas_get_group', - 'group_create_page', 'group_edit_front_page', - 'group_show_front_page', 'group_get_page', - 'group_get_pages', 'group_get_pages2', - 'group_edit', 'group_delete', - 'group_list_users', 'group_list_users_p2', - 'group_invite', 'group_remove_user', - 'group_upload', 'group_upload_final', - 'group_preview_processed_html', 'group_get_activity_stream_summary', - 'group_list_memberships', 'group_list_memberships_p2', - 'group_get_membership', 'group_create_membership', - 'group_update_membership' - ] - } - require_generic = { - 'generic': ['not_found'] - } + @classmethod + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, require_generic, adapter) - register_uris(settings.BASE_URL, requires, adapter) + with requests_mock.Mocker() as m: + register_uris({'course': ['get_by_id'], 'group': ['get_by_id']}, m) - self.course = self.canvas.get_course(1) - self.group = self.canvas.get_group(1) + self.course = self.canvas.get_course(1) + self.group = self.canvas.get_group(1) # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.group) assert isinstance(string, str) # show_front_page() - def test_show_front_page(self): + def test_show_front_page(self, m): + register_uris({'group': ['show_front_page']}, m) + front_page = self.group.show_front_page() assert isinstance(front_page, Page) assert hasattr(front_page, 'url') assert hasattr(front_page, 'title') # create_front_page() - def test_edit_front_page(self): + def test_edit_front_page(self, m): + register_uris({'group': ['edit_front_page']}, m) + new_front_page = self.group.edit_front_page() assert isinstance(new_front_page, Page) assert hasattr(new_front_page, 'url') assert hasattr(new_front_page, 'title') # list_pages() - def test_get_pages(self): + def test_get_pages(self, m): + register_uris({'group': ['get_pages', 'get_pages2']}, m) + pages = self.group.get_pages() page_list = [page for page in pages] assert len(page_list) == 4 assert isinstance(page_list[0], Page) - assert hasattr(page_list[0], 'group_id') + assert hasattr(page_list[0], 'id') assert page_list[0].group_id == self.group.id # create_page() - def test_create_page(self): + def test_create_page(self, m): + register_uris({'group': ['create_page']}, m) + title = 'New Page' new_page = self.group.create_page(wiki_page={'title': title}) assert isinstance(new_page, Page) assert hasattr(new_page, 'title') assert new_page.title == title - assert hasattr(new_page, 'group_id') + assert hasattr(new_page, 'id') assert new_page.group_id == self.group.id - def test_create_page_fail(self): + def test_create_page_fail(self, m): with self.assertRaises(RequiredFieldMissing): self.group.create_page(settings.INVALID_ID) # get_page() - def test_get_page(self): + def test_get_page(self, m): + register_uris({'group': ['get_page']}, m) + url = 'my-url' page = self.group.get_page(url) assert isinstance(page, Page) # edit() - def test_edit(self): + def test_edit(self, m): + register_uris({'group': ['edit']}, m) + new_title = "New Group" response = self.group.edit(description=new_title) assert isinstance(response, Group) assert hasattr(response, 'description') assert response.description == new_title - # # reset for future tests - # self.group = self.group.get_page('my-url') - # delete() - def test_delete(self): + def test_delete(self, m): + register_uris({'group': ['delete']}, m) + group = self.group.delete() assert isinstance(group, Group) assert hasattr(group, 'name') assert hasattr(group, 'description') # invite() - def test_invite(self): + def test_invite(self, m): + register_uris({'group': ['invite']}, m) + user_list = ["1", "2"] response = self.group.invite(user_list) gmembership_list = [groupmembership for groupmembership in response] @@ -121,7 +113,9 @@ def test_invite(self): assert len(gmembership_list) == 2 # list_users() - def test_list_users(self): + def test_list_users(self, m): + register_uris({'group': ['list_users', 'list_users_p2']}, m) + from pycanvas.user import User users = self.group.list_users() user_list = [user for user in users] @@ -129,15 +123,17 @@ def test_list_users(self): assert len(user_list) == 4 # remove_user() - def test_remove_user(self): + def test_remove_user(self, m): + register_uris({'group': ['remove_user']}, m) + from pycanvas.user import User response = self.group.remove_user(1) assert isinstance(response, User) # upload() - def test_upload(self): - import uuid - import os + def test_upload(self, m): + register_uris({'group': ['upload', 'upload_final']}, m) + filename = 'testfile_%s' % uuid.uuid4().hex file = open(filename, 'w+') response = self.group.upload(file) @@ -152,165 +148,142 @@ def test_upload(self): pass # preview_processed_html() - def test_preview_processed_html(self): + def test_preview_processed_html(self, m): + register_uris({'group': ['preview_processed_html']}, m) + html_str = "

processed html

" response = self.group.preview_html(html_str) assert response == html_str - # # get_activity_stream() - not implemented - # def test_activity_stream(self): - # return None - # get_activity_stream_summary() - def test_get_activity_stream_summary(self): + def test_get_activity_stream_summary(self, m): + register_uris({'group': ['activity_stream_summary']}, m) + response = self.group.get_activity_stream_summary() assert len(response) == 2 assert 'type' in response[0] # list_memberships() - def test_list_memberships(self): + def test_list_memberships(self, m): + register_uris({'group': ['list_memberships', 'list_memberships_p2']}, m) + response = self.group.list_memberships() membership_list = [membership for membership in response] assert len(membership_list) == 4 assert isinstance(membership_list[0], GroupMembership) - assert hasattr(membership_list[0], 'group_id') + assert hasattr(membership_list[0], 'id') # get_membership() - def test_get_membership(self): + def test_get_membership(self, m): + register_uris({'group': ['get_membership']}, m) + response = self.group.get_membership(1, "users") assert isinstance(response, GroupMembership) # create_membership() - def test_create_membership(self): + def test_create_membership(self, m): + register_uris({'group': ['create_membership']}, m) + response = self.group.create_membership(1) assert isinstance(response, GroupMembership) # update_membership() - def test_update_membership(self): + def test_update_membership(self, m): + register_uris({'group': ['update_membership_user']}, m) + response = self.group.update_membership(1) assert isinstance(response, GroupMembership) +@requests_mock.Mocker() class TestGroupMembership(unittest.TestCase): - """ - Tests GroupMembership functionality - """ - @classmethod - def setUpClass(self): - requires = { - 'group': [ - 'canvas_get_group', - 'group_get_membership', - 'membership_update', - 'membership_remove_user', - 'membership_remove_self' - ] - } - require_generic = { - 'generic': ['not_found'] - } + @classmethod + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, require_generic, adapter) - register_uris(settings.BASE_URL, requires, adapter) + with requests_mock.Mocker() as m: + register_uris({'group': ['get_by_id', 'get_membership']}, m) - self.group = self.canvas.get_group(1) - self.membership = self.group.get_membership(1, "users") + self.group = self.canvas.get_group(1) + self.membership = self.group.get_membership(1, "users") # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.membership) assert isinstance(string, str) # update() - def test_update(self): + def test_update(self, m): + register_uris({'group': ['update_membership_membership']}, m) + response = self.membership.update(mem_id=1, moderator=False) assert isinstance(response, GroupMembership) # remove_user() - def test_remove_user(self): + def test_remove_user(self, m): + register_uris({'group': ['remove_user']}, m) + response = self.membership.remove_user(1) # the response should be an empty dict that evaluates to false assert not response # remove_self() - def test_remove_self(self): + def test_remove_self(self, m): + register_uris({'group': ['remove_self']}, m) + response = self.membership.remove_self() # the response should be an empty dict that evaluates to false assert not response +@requests_mock.Mocker() class TestGroupCategory(unittest.TestCase): - """ - Tests GroupCategory Item functionality - """ - @classmethod - def setUpClass(self): - requires = { - 'course': [ - 'get_by_id', 'create_group_category', 'list_group_categories' - ], - 'account': [ - 'get_by_id', 'create_group_category', 'list_group_categories' - ], - 'group': [ - 'category_create_group', - 'category_get_category', - 'category_update', - 'category_delete_category', - 'category_list_groups', - 'category_list_users', - 'category_assign_members_true', - 'category_assign_members_false' - ] - } - require_generic = { - 'generic': ['not_found'] - } - - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, require_generic, adapter) - register_uris(settings.BASE_URL, requires, adapter) + @classmethod + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + with requests_mock.Mocker() as m: + register_uris({'course': ['get_by_id', 'create_group_category']}, m) - self.course = self.canvas.get_course(1) - self.group_category = self.course.create_group_category("Test String") + self.course = self.canvas.get_course(1) + self.group_category = self.course.create_group_category("Test String") # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.group_category) assert isinstance(string, str) # create_group() - def test_create_group(self): + def test_create_group(self, m): + register_uris({'group': ['category_create_group']}, m) + test_str = "Test Create Group" response = self.group_category.create_group(name=test_str) assert isinstance(response, Group) assert hasattr(response, 'name') assert response.name == test_str - # get_category() - def test_get_category(self): - response = self.group_category.get_category(1) - assert isinstance(response, GroupCategory) - # update() - def test_update(self): + def test_update(self, m): + register_uris({'group': ['category_update']}, m) + new_name = "Test Update Category" response = self.group_category.update(name=new_name) assert isinstance(response, GroupCategory) # delete_category() - def test_delete_category(self): + def test_delete_category(self, m): + register_uris({'group': ['category_delete_category']}, m) + response = self.group_category.delete() # the response should be an empty dict that evaluates to false assert not response # list_groups() - def test_list_groups(self): + def test_list_groups(self, m): + register_uris({'group': ['category_list_groups']}, m) + response = self.group_category.list_groups() group_list = [group for group in response] assert len(group_list) == 2 @@ -318,8 +291,11 @@ def test_list_groups(self): assert hasattr(group_list[0], 'id') # list_users() - def test_list_users(self): + def test_list_users(self, m): from pycanvas.user import User + + register_uris({'group': ['category_list_users']}, m) + response = self.group_category.list_users() user_list = [user for user in response] assert len(user_list) == 4 @@ -327,10 +303,18 @@ def test_list_users(self): assert hasattr(user_list[0], 'user_id') # assign_members() - def test_assign_members(self): + def test_assign_members(self, m): from pycanvas.progress import Progress from pycanvas.paginated_list import PaginatedList + requires = { + 'group': [ + 'category_assign_members_true', + 'category_assign_members_false' + ] + } + register_uris(requires, m) + result_true = self.group_category.assign_members(sync=True) return_false = self.group_category.assign_members() From 2953ec6f3fd7b87c05e8ba9de380d0853ae60227 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Tue, 20 Dec 2016 14:17:37 -0500 Subject: [PATCH 135/145] register_uris now throws more useful exceptions when something goes wrong parsing the json --- tests/util.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/util.py b/tests/util.py index e4a621df..a6d3c57a 100644 --- a/tests/util.py +++ b/tests/util.py @@ -17,12 +17,18 @@ def register_uris(requirements, requests_mocker): for fixture, objects in requirements.iteritems(): try: - data = json.loads(open('tests/fixtures/%s.json' % (fixture)).read()) + data = json.loads(open('tests/fixtures/{}.json'.format(fixture)).read()) except: - raise ValueError('Fixture %s.json contains invalid JSON.' % (fixture)) + raise ValueError('Fixture {}.json contains invalid JSON.'.format(fixture)) - for obj in objects: - obj = data.get(obj) + if not isinstance(objects, list): + raise TypeError('{} is not a list.'.format(objects)) + + for obj_name in objects: + obj = data.get(obj_name) + + if obj is None: + raise ValueError('{} does not exist in {}.json'.format(obj_name, fixture)) method = requests_mock.ANY if obj['method'] == 'ANY' else obj['method'] url = requests_mock.ANY if obj['endpoint'] == 'ANY' else settings.BASE_URL + obj['endpoint'] From 492a731028f688fcde2658184e554495b1556fe3 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Tue, 20 Dec 2016 14:17:54 -0500 Subject: [PATCH 136/145] updated test_page --- tests/test_page.py | 158 ++++++++++++++++++++++++--------------------- 1 file changed, 85 insertions(+), 73 deletions(-) diff --git a/tests/test_page.py b/tests/test_page.py index f81fee6b..687c7649 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -10,39 +10,34 @@ from tests.util import register_uris +@requests_mock.Mocker() class TestPage(unittest.TestCase): - """ - Test Page methods - """ + @classmethod - def setUpClass(self): - requires = { - 'course': ['get_by_id'], - 'group': ['pages_get_group', 'pages_get_page'], - 'generic': ['not_found'], - 'page': [ - 'get_page', 'edit', 'delete_page', - 'list_revisions', 'list_revisions2', - 'latest_revision', 'get_latest_rev_by_id', - 'get_latest_rev_by_id_group', 'revert_to_revision', - 'revert_to_revision_group' - ] - } - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) - - self.course = self.canvas.get_course(1) - self.group = self.canvas.get_group(1) - self.page_course = self.course.get_page('my-url') - self.page_group = self.group.get_page('my-url') + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + requires = { + 'course': ['get_by_id'], + 'group': ['get_by_id', 'pages_get_page'], + 'page': ['get_page'] + } + register_uris(requires, m) + + self.course = self.canvas.get_course(1) + self.group = self.canvas.get_group(1) + self.page_course = self.course.get_page('my-url') + self.page_group = self.group.get_page('my-url') # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.page_course) assert isinstance(string, str) - def test_edit(self): + def test_edit(self, m): + register_uris({'page': ['edit']}, m) + new_title = "New Page" self.page_course.edit(page={'title': new_title}) @@ -50,132 +45,149 @@ def test_edit(self): assert hasattr(self.page_course, 'title') assert self.page_course.title == new_title - # reset for future tests - self.page_course = self.course.get_page('my-url') + def test_delete(self, m): + register_uris({'page': ['delete_page']}, m) - def test_delete(self): - page = self.course.get_page('my-url') + page = self.page_course deleted_page = page.delete() assert isinstance(deleted_page, Page) - def test_list_revisions(self): + def test_list_revisions(self, m): + register_uris({'page': ['list_revisions', 'list_revisions2']}, m) + revisions = self.page_course.list_revisions() rev_list = [rev for rev in revisions] assert len(rev_list) == 4 assert isinstance(rev_list[0], PageRevision) - def test_show_latest_revision(self): + def test_show_latest_revision(self, m): + register_uris({'page': ['latest_revision']}, m) + revision = self.page_course.show_latest_revision() assert isinstance(revision, PageRevision) - def test_get_revision_by_id_course(self): + def test_get_revision_by_id_course(self, m): + register_uris({'page': ['get_latest_rev_by_id']}, m) + revision = self.page_course.get_revision_by_id(2) assert isinstance(revision, PageRevision) - def test_get_revision_by_id_group(self): + def test_get_revision_by_id_group(self, m): + register_uris({'page': ['get_latest_rev_by_id_group']}, m) + revision = self.page_group.get_revision_by_id(2) assert isinstance(revision, PageRevision) - def test_revert_to_revision_course(self): + def test_revert_to_revision_course(self, m): + register_uris({'page': ['revert_to_revision']}, m) + revision = self.page_course.revert_to_revision(3) assert isinstance(revision, PageRevision) - def test_revert_to_revision_group(self): + def test_revert_to_revision_group(self, m): + register_uris({'page': ['revert_to_revision_group']}, m) + revision = self.page_group.revert_to_revision(3) assert isinstance(revision, PageRevision) # parent_id - def test_parent_id_course(self): + def test_parent_id_course(self, m): assert self.page_course.parent_id == 1 - def test_parent_id_group(self): + def test_parent_id_group(self, m): assert self.page_group.parent_id == 1 - def test_parent_id_no_id(self): + def test_parent_id_no_id(self, m): page = Page(self.canvas._Canvas__requester, {'url': 'my-url'}) with self.assertRaises(ValueError): page.parent_id # parent_type - def test_parent_type_course(self): + def test_parent_type_course(self, m): assert self.page_course.parent_type == 'course' - def test_parent_type_group(self): + def test_parent_type_group(self, m): assert self.page_group.parent_type == 'group' - def test_parent_type_no_id(self): + def test_parent_type_no_id(self, m): page = Page(self.canvas._Canvas__requester, {'url': 'my-url'}) with self.assertRaises(ValueError): page.parent_type # get_parent() - def test_get_parent_course(self): + def test_get_parent_course(self, m): + register_uris({'course': ['get_by_id']}, m) + assert isinstance(self.page_course.get_parent(), Course) - def test_get_parent_group(self): + def test_get_parent_group(self, m): + register_uris({'group': ['get_by_id']}, m) + assert isinstance(self.page_group.get_parent(), Group) +@requests_mock.Mocker() class TestPageRevision(unittest.TestCase): - """ - Tests PageRevision methods - """ + @classmethod - def setUpClass(self): - requires = { - 'course': ['get_by_id', 'get_page'], - 'group': ['pages_get_group', 'pages_get_page'], - 'generic': ['not_found'], - 'page': ['get_latest_rev_by_id', 'get_latest_rev_by_id_group'] - } - - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) - - self.course = self.canvas.get_course(1) - self.group = self.canvas.get_group(1) - self.page_course = self.course.get_page('my-url') - self.page_group = self.group.get_page('my-url') - self.revision = self.page_course.get_revision_by_id(2) - self.group_revision = self.page_group.get_revision_by_id(2) + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + requires = { + 'course': ['get_by_id', 'get_page'], + 'group': ['get_by_id', 'pages_get_page'], + 'page': ['get_latest_rev_by_id', 'get_latest_rev_by_id_group'] + } + register_uris(requires, m) + + self.course = self.canvas.get_course(1) + self.group = self.canvas.get_group(1) + self.page_course = self.course.get_page('my-url') + self.page_group = self.group.get_page('my-url') + self.revision = self.page_course.get_revision_by_id(2) + self.group_revision = self.page_group.get_revision_by_id(2) # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.revision) assert isinstance(string, str) # parent_id - def test_parent_id_course(self): + def test_parent_id_course(self, m): assert self.revision.parent_id == 1 - def test_parent_id_no_id(self): + def test_parent_id_no_id(self, m): page = PageRevision(self.canvas._Canvas__requester, {'url': 'my-url'}) with self.assertRaises(ValueError): page.parent_id # parent_type - def test_parent_type_course(self): + def test_parent_type_course(self, m): assert self.page_course.parent_type == 'course' - def test_parent_type_group(self): + def test_parent_type_group(self, m): assert self.page_group.parent_type == 'group' - def test_parent_type_no_id(self): + def test_parent_type_no_id(self, m): page = PageRevision(self.canvas._Canvas__requester, {'url': 'my-url'}) with self.assertRaises(ValueError): page.parent_type # get_parent() - def test_get_parent_course(self): + def test_get_parent_course(self, m): + register_uris({'course': ['get_by_id']}, m) + assert isinstance(self.revision.get_parent(), Course) - def test_get_parent_group(self): + def test_get_parent_group(self, m): + register_uris({'group': ['get_by_id']}, m) + assert isinstance(self.group_revision.get_parent(), Group) From cc8654b5c542addba1449914a4f4d78edb93dca8 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Tue, 20 Dec 2016 14:54:18 -0500 Subject: [PATCH 137/145] updated test_page_view and test_paginated_list --- tests/test_page_view.py | 25 ++++------ tests/test_paginated_list.py | 90 +++++++++++++++++++++++++----------- 2 files changed, 74 insertions(+), 41 deletions(-) diff --git a/tests/test_page_view.py b/tests/test_page_view.py index 49c23fba..236eb3c4 100644 --- a/tests/test_page_view.py +++ b/tests/test_page_view.py @@ -7,26 +7,21 @@ from util import register_uris +@requests_mock.Mocker() class TestPageView(unittest.TestCase): - """ - Tests PageView functionality. - """ + @classmethod - def setUpClass(self): - requires = { - 'generic': ['not_found'], - 'user': ['get_by_id', 'page_views', 'page_views_p2'] - } + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) + with requests_mock.Mocker() as m: + register_uris({'user': ['get_by_id', 'page_views', 'page_views_p2']}, m) - self.user = self.canvas.get_user(1) - pageviews = self.user.get_page_views() - self.pageview = pageviews[0] + self.user = self.canvas.get_user(1) + pageviews = self.user.get_page_views() + self.pageview = pageviews[0] # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.pageview) assert isinstance(string, str) diff --git a/tests/test_paginated_list.py b/tests/test_paginated_list.py index d11506c2..0ef90f2d 100644 --- a/tests/test_paginated_list.py +++ b/tests/test_paginated_list.py @@ -9,26 +9,18 @@ from tests.util import register_uris +@requests_mock.Mocker() class TestPaginatedList(unittest.TestCase): - """ - Tests PaginatedList functionality. - """ - @classmethod - def setUpClass(self): - requires = { - 'paginated_list': [ - '2_1_page', '4_2_pages_p1', '4_2_pages_p2', '6_3_pages_p1', - '6_3_pages_p2', '6_3_pages_p3', 'empty', 'single', - ] - } - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) + @classmethod + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) self.requester = self.canvas._Canvas__requester - register_uris(settings.BASE_URL, requires, adapter) # various length lists - def test_paginated_list_empty(self): + def test_paginated_list_empty(self, m): + register_uris({'paginated_list': ['empty']}, m) + pag_list = PaginatedList( User, self.requester, @@ -38,7 +30,9 @@ def test_paginated_list_empty(self): item_list = [item for item in pag_list] assert len(item_list) == 0 - def test_paginated_list_single(self): + def test_paginated_list_single(self, m): + register_uris({'paginated_list': ['single']}, m) + pag_list = PaginatedList( User, self.requester, @@ -49,7 +43,9 @@ def test_paginated_list_single(self): assert len(item_list) == 1 assert isinstance(item_list[0], User) - def test_paginated_list_two_one_page(self): + def test_paginated_list_two_one_page(self, m): + register_uris({'paginated_list': ['2_1_page']}, m) + pag_list = PaginatedList( User, self.requester, @@ -60,7 +56,9 @@ def test_paginated_list_two_one_page(self): assert len(item_list) == 2 assert isinstance(item_list[0], User) - def test_paginated_list_four_two_pages(self): + def test_paginated_list_four_two_pages(self, m): + register_uris({'paginated_list': ['4_2_pages_p1', '4_2_pages_p2']}, m) + pag_list = PaginatedList( User, self.requester, @@ -71,7 +69,12 @@ def test_paginated_list_four_two_pages(self): assert len(item_list) == 4 assert isinstance(item_list[0], User) - def test_paginated_list_six_three_pages(self): + def test_paginated_list_six_three_pages(self, m): + requires = { + 'paginated_list': ['6_3_pages_p1', '6_3_pages_p2', '6_3_pages_p3'] + } + register_uris(requires, m) + pag_list = PaginatedList( User, self.requester, @@ -83,7 +86,12 @@ def test_paginated_list_six_three_pages(self): assert isinstance(item_list[0], User) # reusing iterator - def test_iterator(self): + def test_iterator(self, m): + requires = { + 'paginated_list': ['6_3_pages_p1', '6_3_pages_p2', '6_3_pages_p3'] + } + register_uris(requires, m) + pag_list = PaginatedList( User, self.requester, @@ -95,7 +103,12 @@ def test_iterator(self): assert cmp(list_1, list_2) == 0 # get item - def test_getitem_first(self): + def test_getitem_first(self, m): + requires = { + 'paginated_list': ['6_3_pages_p1', '6_3_pages_p2', '6_3_pages_p3'] + } + register_uris(requires, m) + pag_list = PaginatedList( User, self.requester, @@ -105,7 +118,12 @@ def test_getitem_first(self): first_item = pag_list[0] assert isinstance(first_item, User) - def test_getitem_second_page(self): + def test_getitem_second_page(self, m): + requires = { + 'paginated_list': ['6_3_pages_p1', '6_3_pages_p2', '6_3_pages_p3'] + } + register_uris(requires, m) + pag_list = PaginatedList( User, self.requester, @@ -116,7 +134,12 @@ def test_getitem_second_page(self): assert isinstance(third_item, User) # slicing - def test_slice_beginning(self): + def test_slice_beginning(self, m): + requires = { + 'paginated_list': ['6_3_pages_p1', '6_3_pages_p2', '6_3_pages_p3'] + } + register_uris(requires, m) + pag_list = PaginatedList( User, self.requester, @@ -130,7 +153,12 @@ def test_slice_beginning(self): assert hasattr(item_list[0], 'id') assert item_list[0].id == '1' - def test_slice_middle(self): + def test_slice_middle(self, m): + requires = { + 'paginated_list': ['6_3_pages_p1', '6_3_pages_p2', '6_3_pages_p3'] + } + register_uris(requires, m) + pag_list = PaginatedList( User, self.requester, @@ -144,7 +172,12 @@ def test_slice_middle(self): assert hasattr(item_list[0], 'id') assert item_list[0].id == '3' - def test_slice_end(self): + def test_slice_end(self, m): + requires = { + 'paginated_list': ['6_3_pages_p1', '6_3_pages_p2', '6_3_pages_p3'] + } + register_uris(requires, m) + pag_list = PaginatedList( User, self.requester, @@ -159,7 +192,12 @@ def test_slice_end(self): assert item_list[0].id == '5' # __repr__() - def test_repr(self): + def test_repr(self, m): + requires = { + 'paginated_list': ['6_3_pages_p1', '6_3_pages_p2', '6_3_pages_p3'] + } + register_uris(requires, m) + pag_list = PaginatedList( User, self.requester, From b63584132ace1d078ba4ea174770ef8d0533d0d9 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Tue, 20 Dec 2016 15:09:15 -0500 Subject: [PATCH 138/145] Updated test_progress, test_quiz, and test_requester --- tests/test_progress.py | 37 +++++++++++++--------------- tests/test_quiz.py | 32 ++++++++++++------------ tests/test_requester.py | 54 +++++++++++++++++++++++------------------ 3 files changed, 63 insertions(+), 60 deletions(-) diff --git a/tests/test_progress.py b/tests/test_progress.py index cfdf9b10..cf3b8b2b 100644 --- a/tests/test_progress.py +++ b/tests/test_progress.py @@ -4,40 +4,37 @@ import settings from pycanvas.canvas import Canvas -from pycanvas.course import Course -from pycanvas.group import Group, GroupCategory from pycanvas.progress import Progress from util import register_uris +@requests_mock.Mocker() class TestProgress(unittest.TestCase): - """ - Tests Progress functionality. - """ + @classmethod - def setUpClass(self): - requires = { - 'generic': ['not_found'], - 'course': ['get_by_id', 'create_group_category'], - 'group': ['category_create_group', 'category_assign_members_false'], - 'progress': ['progress_query'] - } + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) + with requests_mock.Mocker() as m: + requires = { + 'course': ['get_by_id', 'create_group_category'], + 'group': ['category_assign_members_false'] + } - self.course = self.canvas.get_course(1) - self.group_category = self.course.create_group_category("Test String") + register_uris(requires, m) - self.progress = self.group_category.assign_members() + self.course = self.canvas.get_course(1) + self.group_category = self.course.create_group_category("Test String") + self.progress = self.group_category.assign_members() # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.progress) assert isinstance(string, str) # query() - def test_query(self): + def test_query(self, m): + register_uris({'progress': ['progress_query']}, m) + response = self.progress.query() assert isinstance(response, Progress) diff --git a/tests/test_quiz.py b/tests/test_quiz.py index c0d21253..7945077d 100644 --- a/tests/test_quiz.py +++ b/tests/test_quiz.py @@ -8,32 +8,28 @@ from tests.util import register_uris +@requests_mock.Mocker() class TestQuiz(unittest.TestCase): - """ - Tests Quiz functionality - """ + @classmethod - def setUpClass(self): - requires = { - 'course': ['get_by_id'], - 'generic': ['not_found'], - 'quiz': ['delete', 'edit', 'get_by_id'], - } + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) + with requests_mock.Mocker() as m: + register_uris({'course': ['get_by_id'], 'quiz': ['get_by_id']}, m) - self.course = self.canvas.get_course(1) - self.quiz = self.course.get_quiz(1) + self.course = self.canvas.get_course(1) + self.quiz = self.course.get_quiz(1) # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.quiz) assert isinstance(string, str) # edit() - def test_edit(self): + def test_edit(self, m): + register_uris({'quiz': ['edit']}, m) + title = 'New Title' edited_quiz = self.quiz.edit(quiz={'title': title}) @@ -44,7 +40,9 @@ def test_edit(self): assert edited_quiz.course_id == self.course.id # delete() - def test_delete(self): + def test_delete(self, m): + register_uris({'quiz': ['delete']}, m) + title = "Great Title" deleted_quiz = self.quiz.delete(quiz={'title': title}) diff --git a/tests/test_requester.py b/tests/test_requester.py index 3b6f6b7a..57bcfb04 100644 --- a/tests/test_requester.py +++ b/tests/test_requester.py @@ -11,57 +11,65 @@ from tests.util import register_uris +@requests_mock.Mocker() class TestRequester(unittest.TestCase): - """ - Tests Requester functionality. - """ + @classmethod - def setUpClass(self): - requires = { - 'requests': [ - '400', '401_invalid_access_token', '401_unauthorized', '404', - '500', 'delete', 'get', 'post', 'put' - ] - } - - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) self.requester = self.canvas._Canvas__requester - register_uris(settings.BASE_URL, requires, adapter) # request() - def test_request_get(self): + def test_request_get(self, m): + register_uris({'requests': ['get']}, m) + response = self.requester.request('GET', 'fake_get_request') assert response.status_code == 200 - def test_request_post(self): + def test_request_post(self, m): + register_uris({'requests': ['post']}, m) + response = self.requester.request('POST', 'fake_post_request') assert response.status_code == 200 - def test_request_delete(self): + def test_request_delete(self, m): + register_uris({'requests': ['delete']}, m) + response = self.requester.request('DELETE', 'fake_delete_request') assert response.status_code == 200 - def test_request_put(self): + def test_request_put(self, m): + register_uris({'requests': ['put']}, m) + response = self.requester.request('PUT', 'fake_put_request') assert response.status_code == 200 - def test_request_400(self): + def test_request_400(self, m): + register_uris({'requests': ['400']}, m) + with self.assertRaises(BadRequest): self.requester.request('GET', '400') - def test_request_401_InvalidAccessToken(self): + def test_request_401_InvalidAccessToken(self, m): + register_uris({'requests': ['401_invalid_access_token']}, m) + with self.assertRaises(InvalidAccessToken): self.requester.request('GET', '401_invalid_access_token') - def test_request_401_Unauthorized(self): + def test_request_401_Unauthorized(self, m): + register_uris({'requests': ['401_unauthorized']}, m) + with self.assertRaises(Unauthorized): self.requester.request('GET', '401_unauthorized') - def test_request_404(self): + def test_request_404(self, m): + register_uris({'requests': ['404']}, m) + with self.assertRaises(ResourceDoesNotExist): self.requester.request('GET', '404') - def test_request_500(self): + def test_request_500(self, m): + register_uris({'requests': ['500']}, m) + with self.assertRaises(CanvasException): self.requester.request('GET', '500') From 6f63f4cb1dcd9dd25ee7010401f8a135922fc0af Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Tue, 20 Dec 2016 15:24:57 -0500 Subject: [PATCH 139/145] Changed exception to use __repr__ instead of __str__ --- tests/util.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/util.py b/tests/util.py index a6d3c57a..68fc613f 100644 --- a/tests/util.py +++ b/tests/util.py @@ -28,7 +28,10 @@ def register_uris(requirements, requests_mocker): obj = data.get(obj_name) if obj is None: - raise ValueError('{} does not exist in {}.json'.format(obj_name, fixture)) + raise ValueError('{} does not exist in {}.json'.format( + obj_name.__repr__(), + fixture + )) method = requests_mock.ANY if obj['method'] == 'ANY' else obj['method'] url = requests_mock.ANY if obj['endpoint'] == 'ANY' else settings.BASE_URL + obj['endpoint'] From 2ed2711128830fe0125796c2682c1f8d292c3319 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Tue, 20 Dec 2016 15:33:46 -0500 Subject: [PATCH 140/145] Updated test_module and test_section --- tests/test_module.py | 111 ++++++++++++++++++++++-------------------- tests/test_section.py | 49 ++++++++++--------- 2 files changed, 84 insertions(+), 76 deletions(-) diff --git a/tests/test_module.py b/tests/test_module.py index 493238bc..f4e78d69 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -9,29 +9,23 @@ from tests.util import register_uris +@requests_mock.Mocker() class TestModule(unittest.TestCase): - """ - Tests Module functionality - """ + @classmethod - def setUpClass(self): - requires = { - 'course': ['get_by_id', 'get_module_by_id'], - 'module': [ - 'edit', 'delete', 'relock', 'list_module_items', - 'list_module_items2', 'get_module_item_by_id', 'create_module_item', - ] - } - - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) - - self.course = self.canvas.get_course(1) - self.module = self.course.get_module(1) + 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_module_by_id']}, m) + + self.course = self.canvas.get_course(1) + self.module = self.course.get_module(1) # edit() - def test_edit_module(self): + def test_edit_module(self, m): + register_uris({'module': ['edit']}, m) + name = 'New Name' edited_module = self.module.edit(module={'name': name}) @@ -42,7 +36,9 @@ def test_edit_module(self): assert edited_module.course_id == self.course.id # delete() - def test_delete_module(self): + def test_delete_module(self, m): + register_uris({'module': ['delete']}, m) + deleted_module = self.module.delete() assert isinstance(deleted_module, Module) @@ -50,7 +46,9 @@ def test_delete_module(self): assert deleted_module.course_id == self.course.id # relock() - def test_relock(self): + def test_relock(self, m): + register_uris({'module': ['relock']}, m) + relocked_module = self.module.relock() assert isinstance(relocked_module, Module) @@ -58,7 +56,9 @@ def test_relock(self): assert relocked_module.course_id == self.course.id # list_module_items() - def test_list_module_items(self): + def test_list_module_items(self, m): + register_uris({'module': ['list_module_items', 'list_module_items2']}, m) + module_items = self.module.list_module_items() module_item_list = [module_item for module_item in module_items] @@ -68,7 +68,9 @@ def test_list_module_items(self): assert module_item_list[0].course_id == self.course.id # get_module_item() - def test_get_module_item(self): + def test_get_module_item(self, m): + register_uris({'module': ['get_module_item_by_id']}, m) + module_item = self.module.get_module_item(1) assert isinstance(module_item, ModuleItem) @@ -76,7 +78,9 @@ def test_get_module_item(self): assert module_item.course_id == self.course.id # create_module_item() - def test_create_module_item(self): + def test_create_module_item(self, m): + register_uris({'module': ['create_module_item']}, m) + module_item = self.module.create_module_item( module_item={ 'type': 'Page', @@ -87,49 +91,46 @@ def test_create_module_item(self): assert hasattr(module_item, 'course_id') assert module_item.course_id == self.course.id - def test_create_module_item_fail1(self): + def test_create_module_item_fail1(self, m): with self.assertRaises(RequiredFieldMissing): self.module.create_module_item( module_item={'content_id': 1} ) - def test_create_module_item_fail2(self): + def test_create_module_item_fail2(self, m): with self.assertRaises(RequiredFieldMissing): self.module.create_module_item( module_item={'type': 'Page'} ) # __str__ - def test__str__(self): + def test__str__(self, m): string = str(self.module) assert isinstance(string, str) +@requests_mock.Mocker() class TestModuleItem(unittest.TestCase): - """ - Tests Module Item functionality - """ + @classmethod - def setUpClass(self): - requires = { - 'course': ['get_by_id', 'get_module_by_id'], - 'module': [ - 'get_module_item_by_id', 'edit_module_item', - 'delete_module_item', 'complete_module_item', - 'uncomplete_module_item' - ] - } - - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) - - self.course = self.canvas.get_course(1) - self.module = self.course.get_module(1) - self.module_item = self.module.get_module_item(1) + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + requires = { + 'course': ['get_by_id', 'get_module_by_id'], + 'module': ['get_module_item_by_id'] + } + register_uris(requires, m) + + self.course = self.canvas.get_course(1) + self.module = self.course.get_module(1) + self.module_item = self.module.get_module_item(1) # edit() - def test_edit_module_item(self): + def test_edit_module_item(self, m): + register_uris({'module': ['edit_module_item']}, m) + title = 'New Title' edited_module_item = self.module_item.edit( module_item={'title': title} @@ -142,7 +143,9 @@ def test_edit_module_item(self): assert edited_module_item.course_id == self.course.id # delete() - def test_delete(self): + def test_delete(self, m): + register_uris({'module': ['delete_module_item']}, m) + deleted_module_item = self.module_item.delete() assert isinstance(deleted_module_item, ModuleItem) @@ -150,7 +153,9 @@ def test_delete(self): assert deleted_module_item.course_id == self.course.id # complete(course_id, True) - def test_complete(self): + def test_complete(self, m): + register_uris({'module': ['complete_module_item']}, m) + completed_module_item = self.module_item.complete() assert isinstance(completed_module_item, ModuleItem) @@ -159,7 +164,9 @@ def test_complete(self): assert completed_module_item.course_id == self.course.id # complete(course_id, False) - def test_uncomplete(self): + def test_uncomplete(self, m): + register_uris({'module': ['uncomplete_module_item']}, m) + completed_module_item = self.module_item.uncomplete() assert isinstance(completed_module_item, ModuleItem) @@ -167,6 +174,6 @@ def test_uncomplete(self): assert hasattr(completed_module_item, 'course_id') assert completed_module_item.course_id == self.course.id - def test__str__(self): + def test__str__(self, m): string = str(self.module_item) assert isinstance(string, str) diff --git a/tests/test_section.py b/tests/test_section.py index b60b1c3e..109adbd5 100644 --- a/tests/test_section.py +++ b/tests/test_section.py @@ -8,56 +8,57 @@ from tests.util import register_uris +@requests_mock.Mocker() class TestSection(unittest.TestCase): - """ - Tests core Section functionality - """ + @classmethod - def setUpClass(self): - requires = { - 'generic': ['not_found'], - 'section': [ - 'crosslist_section', 'decross_section', 'delete', - 'edit', 'get_by_id', 'list_enrollments', - 'list_enrollments_2' - ] - } - - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) - - self.section = self.canvas.get_section(1) + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + register_uris({'section': ['get_by_id']}, m) + + self.section = self.canvas.get_section(1) # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.section) assert isinstance(string, str) # list_enrollments() - def test_get_enrollments(self): + def test_get_enrollments(self, m): + register_uris({'section': ['list_enrollments', 'list_enrollments_2']}, m) + enrollments = self.section.get_enrollments() enrollment_list = [enrollment for enrollment in enrollments] assert len(enrollment_list) == 4 assert isinstance(enrollment_list[0], Enrollment) - def test_cross_list_section(self): + def test_cross_list_section(self, m): + register_uris({'section': ['crosslist_section']}, m) + section = self.section.cross_list_section(2) assert isinstance(section, Section) - def test_decross_list_section(self): + def test_decross_list_section(self, m): + register_uris({'section': ['decross_section']}, m) + section = self.section.decross_list_section() assert isinstance(section, Section) - def test_edit(self): + def test_edit(self, m): + register_uris({'section': ['edit']}, m) + edit = self.section.edit() assert isinstance(edit, Section) - def test_delete(self): + def test_delete(self, m): + register_uris({'section': ['delete']}, m) + deleted_section = self.section.delete() assert isinstance(deleted_section, Section) From 695420721e8891fd9d51a3334b8724d1ba9782ec Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Tue, 20 Dec 2016 15:48:54 -0500 Subject: [PATCH 141/145] Updated test_uploader. Removed unused fixtures --- tests/fixtures/uploader.json | 14 ----------- tests/test_uploader.py | 48 ++++++++++++++++++------------------ 2 files changed, 24 insertions(+), 38 deletions(-) diff --git a/tests/fixtures/uploader.json b/tests/fixtures/uploader.json index 22b6c174..50bc1e29 100644 --- a/tests/fixtures/uploader.json +++ b/tests/fixtures/uploader.json @@ -17,20 +17,6 @@ "url": "great_url_success" } }, - "upload_response_no_upload_url": { - "method": "POST", - "endpoint": "upload_response_no_upload_url", - "data": { - "some_param": "not_upload" - } - }, - "upload_response_no_upload_params": { - "method": "POST", - "endpoint": "upload_response_no_upload_params", - "data": { - "upload_url": "http://example.com/api/v1/upload_response_upload_url" - } - }, "upload_response_fail": { "method": "POST", "endpoint": "upload_response_fail", diff --git a/tests/test_uploader.py b/tests/test_uploader.py index 5101996d..958bf105 100644 --- a/tests/test_uploader.py +++ b/tests/test_uploader.py @@ -10,25 +10,14 @@ from tests.util import register_uris +@requests_mock.Mocker() class TestUploader(unittest.TestCase): @classmethod - def setUpClass(self): - requires = { - 'uploader': [ - 'upload_response', 'upload_response_upload_url', - 'upload_response_no_upload_url', 'upload_response_no_upload_params', - 'upload_response_fail', 'upload_fail' - ] - } - - adapter = requests_mock.Adapter() - register_uris(settings.BASE_URL, requires, adapter) - - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) self.requester = self.canvas._Canvas__requester - def setUp(self): self.filename = 'testfile_%s' % uuid.uuid4().hex self.file = open(self.filename, 'w+') @@ -41,7 +30,12 @@ def tearDown(self): pass # start() - def test_start(self): + def test_start(self, m): + requires = { + 'uploader': ['upload_response', 'upload_response_upload_url'] + } + register_uris(requires, m) + uploader = Uploader(self.requester, 'upload_response', self.file) result = uploader.start() @@ -49,8 +43,12 @@ def test_start(self): assert isinstance(result[1], dict) assert 'url' in result[1] - # start() - def test_start_path(self): + def test_start_path(self, m): + requires = { + 'uploader': ['upload_response', 'upload_response_upload_url'] + } + register_uris(requires, m) + uploader = Uploader(self.requester, 'upload_response', self.filename) result = uploader.start() @@ -58,23 +56,25 @@ def test_start_path(self): assert isinstance(result[1], dict) assert 'url' in result[1] - # start() - def test_start_file_does_not_exist(self): + def test_start_file_does_not_exist(self, m): with self.assertRaises(IOError): Uploader(self.requester, 'upload_response', 'test_file_not_real.xyz') # upload() - def test_upload_no_upload_url(self): + def test_upload_no_upload_url(self, m): with self.assertRaises(Exception): Uploader(self.requester, 'upload_response_no_upload_url', self.filename).start() - # upload() - def test_upload_no_upload_params(self): + def test_upload_no_upload_params(self, m): with self.assertRaises(Exception): Uploader(self.requester, 'upload_response_no_upload_params', self.filename).start() - # upload() - def test_upload_fail(self): + def test_upload_fail(self, m): + requires = { + 'uploader': ['upload_fail', 'upload_response_fail'] + } + register_uris(requires, m) + uploader = Uploader(self.requester, 'upload_response_fail', self.file) result = uploader.start() From 21269c3585a065039a517b145f3b50667c481e01 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Thu, 22 Dec 2016 12:52:40 -0500 Subject: [PATCH 142/145] updated test_user and test_util --- tests/test_user.py | 111 +++++++++++++++++++++++++-------------------- tests/test_util.py | 51 ++++++++++----------- 2 files changed, 85 insertions(+), 77 deletions(-) diff --git a/tests/test_user.py b/tests/test_user.py index 762e2491..14a6f21a 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -16,44 +16,36 @@ from tests.util import register_uris +@requests_mock.Mocker() class TestUser(unittest.TestCase): - """ - Tests User functionality. - """ + @classmethod - def setUpClass(self): - requires = { - 'generic': ['not_found'], - 'user': [ - 'avatars', 'avatars_p2', 'color', 'color_update', 'colors', - 'courses', 'courses_p2', 'edit', 'get_by_id', 'get_by_id_2', - 'get_user_assignments', 'get_user_assignments2', - 'list_enrollments', 'list_enrollments_2', 'list_groups', 'list_groups2', 'merge', - 'missing_sub', 'missing_sub_p2', 'page_views', 'page_views_p2', - 'profile', 'update_settings', 'upload', 'upload_final' - ] - } - - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) - - self.user = self.canvas.get_user(1) + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + register_uris({'user': ['get_by_id']}, m) + + self.user = self.canvas.get_user(1) # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.user) assert isinstance(string, str) # get_profile() - def test_get_profile(self): + def test_get_profile(self, m): + register_uris({'user': ['profile']}, m) + profile = self.user.get_profile() assert isinstance(profile, dict) assert 'name' in profile # get_page_views() - def test_get_page_views(self): + def test_get_page_views(self, m): + register_uris({'user': ['page_views', 'page_views_p2']}, m) + page_views = self.user.get_page_views() page_view_list = [view for view in page_views] @@ -61,7 +53,9 @@ def test_get_page_views(self): assert isinstance(page_view_list[0], PageView) # get_courses() - def test_get_courses(self): + def test_get_courses(self, m): + register_uris({'user': ['courses', 'courses_p2']}, m) + courses = self.user.get_courses() course_list = [course for course in courses] @@ -69,7 +63,9 @@ def test_get_courses(self): assert isinstance(course_list[0], Course) # get_missing_submissions() - def test_get_missing_submissions(self): + def test_get_missing_submissions(self, m): + register_uris({'user': ['missing_sub', 'missing_sub_p2']}, m) + missing_assigments = self.user.get_missing_submissions() assignment_list = [assignment for assignment in missing_assigments] @@ -77,7 +73,9 @@ def test_get_missing_submissions(self): assert isinstance(assignment_list[0], Assignment) # update_settings() - def test_update_settings(self): + def test_update_settings(self, m): + register_uris({'user': ['update_settings']}, m) + settings = self.user.update_settings(manual_mark_as_read=True) assert isinstance(settings, dict) @@ -85,7 +83,9 @@ def test_update_settings(self): assert settings['manual_mark_as_read'] is True # get_color() - def test_get_color(self): + def test_get_color(self, m): + register_uris({'user': ['color']}, m) + color = self.user.get_color("course_1") assert isinstance(color, dict) @@ -93,7 +93,9 @@ def test_get_color(self): assert color['hexcode'] == "#abc123" # get_colors() - def test_get_colors(self): + def test_get_colors(self, m): + register_uris({'user': ['colors']}, m) + colors = self.user.get_colors() assert isinstance(colors, dict) @@ -101,7 +103,9 @@ def test_get_colors(self): assert isinstance(colors['custom_colors'], dict) # update_color() - def test_update_color(self): + def test_update_color(self, m): + register_uris({'user': ['color_update']}, m) + new_hexcode = "#f00f00" color = self.user.update_color("course_1", new_hexcode) @@ -109,7 +113,9 @@ def test_update_color(self): assert 'hexcode' in color assert color['hexcode'] == new_hexcode - def test_update_color_no_hashtag(self): + def test_update_color_no_hashtag(self, m): + register_uris({'user': ['color_update']}, m) + new_hexcode = "f00f00" color = self.user.update_color("course_1", new_hexcode) @@ -118,7 +124,9 @@ def test_update_color_no_hashtag(self): assert color['hexcode'] == "#" + new_hexcode # edit() - def test_edit(self): + def test_edit(self, m): + register_uris({'user': ['edit']}, m) + new_name = "New User Name" self.user.edit(user={'name': new_name}) @@ -126,21 +134,19 @@ def test_edit(self): assert hasattr(self.user, 'name') assert self.user.name == new_name - # reset for future tests - self.user = self.canvas.get_user(1) - # merge_into() - def test_merge_into_id(self): + def test_merge_into_id(self, m): + register_uris({'user': ['merge']}, m) + self.user.merge_into(2) assert isinstance(self.user, User) assert hasattr(self.user, 'name') assert self.user.name == 'John Smith' - # reset for future tests - self.user = self.canvas.get_user(1) + def test_merge_into_user(self, m): + register_uris({'user': ['get_by_id_2', 'merge']}, m) - def test_merge_into_user(self): other_user = self.canvas.get_user(2) self.user.merge_into(other_user) @@ -148,11 +154,10 @@ def test_merge_into_user(self): assert hasattr(self.user, 'name') assert self.user.name == 'John Smith' - # reset for future tests - self.user = self.canvas.get_user(1) - # get_avatars() - def test_get_avatars(self): + def test_get_avatars(self, m): + register_uris({'user': ['avatars', 'avatars_p2']}, m) + avatars = self.user.get_avatars() avatar_list = [avatar for avatar in avatars] @@ -160,17 +165,19 @@ def test_get_avatars(self): assert isinstance(avatar_list[0], Avatar) # get_assignments() - def test_user_assignments(self): - user = self.canvas.get_user(1) + def test_user_assignments(self, m): + register_uris({'user': ['get_user_assignments', 'get_user_assignments2']}, m) - assignments = user.get_assignments(1) + assignments = self.user.get_assignments(1) assignment_list = [assignment for assignment in assignments] assert isinstance(assignments[0], Assignment) assert len(assignment_list) == 4 - #list_enrollments() - def test_list_enrollments(self): + # list_enrollments() + def test_list_enrollments(self, m): + register_uris({'user': ['list_enrollments', 'list_enrollments_2']}, m) + enrollments = self.user.get_enrollments() enrollment_list = [enrollment for enrollment in enrollments] @@ -178,7 +185,9 @@ def test_list_enrollments(self): assert isinstance(enrollment_list[0], Enrollment) # upload() - def test_upload(self): + def test_upload(self, m): + register_uris({'user': ['upload', 'upload_final']}, m) + filename = 'testfile_%s' % uuid.uuid4().hex file = open(filename, 'w+') @@ -195,7 +204,9 @@ def test_upload(self): except OSError: pass - def test_list_groups(self): + def test_list_groups(self, m): + register_uris({'user': ['list_groups', 'list_groups2']}, m) + groups = self.user.list_groups() group_list = [group for group in groups] diff --git a/tests/test_util.py b/tests/test_util.py index 3ef7e5b0..a6013b8f 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -10,40 +10,33 @@ from tests.util import register_uris -class TestCourse(unittest.TestCase): - """ - Tests utility methods - """ - @classmethod - def setUpClass(self): - requires = { - 'generic': ['not_found'], - 'user': ['get_by_id', 'course_nickname'] - } +@requests_mock.Mocker() +class TestUtil(unittest.TestCase): - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) + @classmethod + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) # combine_kwargs() - def test_combine_kwargs_empty(self): + def test_combine_kwargs_empty(self, m): + result = combine_kwargs() self.assertIsInstance(result, dict) self.assertEqual(result, {}) - def test_combine_kwargs_single(self): + def test_combine_kwargs_single(self, m): result = combine_kwargs(var='test') self.assertIsInstance(result, dict) self.assertTrue('var' in result) self.assertEqual(result['var'], 'test') - def test_combine_kwargs_single_dict(self): + def test_combine_kwargs_single_dict(self, m): result = combine_kwargs(var={'foo': 'bar'}) self.assertIsInstance(result, dict) self.assertTrue('var[foo]' in result) self.assertEqual(result['var[foo]'], 'bar') - def test_combine_kwargs_multiple_dicts(self): + def test_combine_kwargs_multiple_dicts(self, m): result = combine_kwargs( var1={'foo': 'bar'}, var2={'fizz': 'buzz'} @@ -57,7 +50,7 @@ def test_combine_kwargs_multiple_dicts(self): self.assertIn('var2[fizz]', result) self.assertEqual(result['var2[fizz]'], 'buzz') - def test_combine_kwargs_multiple_mixed(self): + def test_combine_kwargs_multiple_mixed(self, m): result = combine_kwargs( var1=True, var2={'fizz': 'buzz'}, @@ -75,7 +68,7 @@ def test_combine_kwargs_multiple_mixed(self): self.assertEqual(result['var3'], 'foo') self.assertEqual(result['var4'], 42) - def test_combine_kwargs_nested_dict(self): + def test_combine_kwargs_nested_dict(self, m): result = combine_kwargs(dict={ 'key': {'subkey': 'value'} }) @@ -85,7 +78,7 @@ def test_combine_kwargs_nested_dict(self): self.assertIn('dict[key][subkey]', result) self.assertEqual(result['dict[key][subkey]'], 'value') - def test_combine_kwargs_multiple_nested_dicts(self): + def test_combine_kwargs_multiple_nested_dicts(self, m): result = combine_kwargs( dict1={ 'key1': { @@ -132,7 +125,7 @@ def test_combine_kwargs_multiple_nested_dicts(self): self.assertEqual(result['dict2[key2][subkey2-1]'], 'value2-1') self.assertEqual(result['dict2[key2][subkey2-2]'], 'value2-2') - def test_combine_kwargs_super_nested_dict(self): + def test_combine_kwargs_super_nested_dict(self, m): result = combine_kwargs( big_dict={'a': {'b': {'c': {'d': {'e': 'We need to go deeper'}}}}} ) @@ -141,7 +134,7 @@ def test_combine_kwargs_super_nested_dict(self): self.assertIn('big_dict[a][b][c][d][e]', result) self.assertEqual(result['big_dict[a][b][c][d][e]'], 'We need to go deeper') - def test_combine_kwargs_the_gauntlet(self): + def test_combine_kwargs_the_gauntlet(self, m): result = combine_kwargs( foo='bar', fb={ @@ -211,23 +204,25 @@ def test_combine_kwargs_the_gauntlet(self): self.assertTrue(all(key in result for key in expected_keys)) # obj_or_id() - def test_obj_or_id_int(self): + def test_obj_or_id_int(self, m): user_id = obj_or_id(1, 'user_id', (User,)) assert isinstance(user_id, int) assert user_id == 1 - def test_obj_or_id_str_valid(self): + def test_obj_or_id_str_valid(self, m): user_id = obj_or_id("1", 'user_id', (User,)) assert isinstance(user_id, int) assert user_id == 1 - def test_obj_or_id_str_invalid(self): + def test_obj_or_id_str_invalid(self, m): with self.assertRaises(TypeError): obj_or_id("1a", 'user_id', (User,)) - def test_obj_or_id_obj(self): + def test_obj_or_id_obj(self, m): + register_uris({'user': ['get_by_id']}, m) + user = self.canvas.get_user(1) user_id = obj_or_id(user, 'user_id', (User,)) @@ -235,7 +230,9 @@ def test_obj_or_id_obj(self): assert isinstance(user_id, int) assert user_id == 1 - def test_obj_or_id_obj_no_id(self): + def test_obj_or_id_obj_no_id(self, m): + register_uris({'user': ['course_nickname']}, m) + nick = self.canvas.get_course_nickname(1) with self.assertRaises(TypeError): From aa728d43fdd0c425409129cf3120e038b3d365b2 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Thu, 22 Dec 2016 14:00:27 -0500 Subject: [PATCH 143/145] Fixed an issue with missing fixtures in test_uploader. Made the exception more specific for failed uploads --- pycanvas/upload.py | 4 ++-- tests/fixtures/uploader.json | 14 ++++++++++++++ tests/test_uploader.py | 8 ++++++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/pycanvas/upload.py b/pycanvas/upload.py index 98fb9aa4..060b9929 100644 --- a/pycanvas/upload.py +++ b/pycanvas/upload.py @@ -54,10 +54,10 @@ def upload(self, response): """ response = response.json() if not response.get('upload_url'): - raise Exception('Bad API response. No upload_url.') + raise ValueError('Bad API response. No upload_url.') if not response.get('upload_params'): - raise Exception('Bad API response. No upload_params.') + raise ValueError('Bad API response. No upload_params.') kwargs = response.get('upload_params') kwargs['file'] = self.file diff --git a/tests/fixtures/uploader.json b/tests/fixtures/uploader.json index 50bc1e29..efcdb107 100644 --- a/tests/fixtures/uploader.json +++ b/tests/fixtures/uploader.json @@ -17,6 +17,20 @@ "url": "great_url_success" } }, + "upload_response_no_upload_url": { + "method": "POST", + "endpoint": "upload_response_no_upload_url", + "data": { + "some_param": "not_upload" + } + }, + "upload_response_no_upload_params": { + "method": "POST", + "endpoint": "upload_response_no_upload_params", + "data": { + "upload_url": "mock://example.com/api/v1/upload_response_upload_url" + } + }, "upload_response_fail": { "method": "POST", "endpoint": "upload_response_fail", diff --git a/tests/test_uploader.py b/tests/test_uploader.py index 958bf105..8991f428 100644 --- a/tests/test_uploader.py +++ b/tests/test_uploader.py @@ -62,11 +62,15 @@ def test_start_file_does_not_exist(self, m): # upload() def test_upload_no_upload_url(self, m): - with self.assertRaises(Exception): + register_uris({'uploader': ['upload_response_no_upload_url']}, m) + + with self.assertRaises(ValueError): Uploader(self.requester, 'upload_response_no_upload_url', self.filename).start() def test_upload_no_upload_params(self, m): - with self.assertRaises(Exception): + register_uris({'uploader': ['upload_response_no_upload_params']}, m) + + with self.assertRaises(ValueError): Uploader(self.requester, 'upload_response_no_upload_params', self.filename).start() def test_upload_fail(self, m): From d12388f85b1517187e3006d7633dd9d15cb7b223 Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Tue, 3 Jan 2017 17:57:23 -0500 Subject: [PATCH 144/145] updated setup.py --- pycanvas/__init__.py | 4 ++++ setup.py | 31 +++++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/pycanvas/__init__.py b/pycanvas/__init__.py index ef3e9572..de512e8d 100644 --- a/pycanvas/__init__.py +++ b/pycanvas/__init__.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- + +__version__ = '0.2' + import exceptions import canvas import canvas_object diff --git a/setup.py b/setup.py index 9c42ff7e..f72d6f66 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,37 @@ +import re from setuptools import setup +# get version number +with open('pycanvas/__init__.py', 'r') as fd: + version = re.search( + r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', + fd.read(), + re.MULTILINE + ).group(1) + +if not version: + raise RuntimeError('Cannot find version information') + setup( name='pycanvas', - version='0.1.2', + version=version, description='API wrapper for the Canvas LMS', - url='https://example.com/changeme/pycanvas/', + url='https://github.com/ucfopen/PyCanvas', author='Techrangers (University of Central Florida)', author_email='pycanvas@example.com', - license='Some cool UCF license', + license='MIT License', packages=['pycanvas'], + include_package_data=True, install_requires=['requests'], - zip_safe=False + zip_safe=False, + classifiers=[ + 'Development Status :: 2 - Pre-Alpha' + 'Intended Audience :: Developers', + 'Intended Audience :: Education', + 'Intended Audience :: Information Technology', + 'License :: OSI Approved :: MIT License' + 'Operating System :: OS Independent' + 'Programming Language :: Python', + 'Topic :: Software Development :: Libraries', + ], ) From 4cab2f734fdb659662075f8d6fa29188dad4953e Mon Sep 17 00:00:00 2001 From: Matthew Emond Date: Wed, 4 Jan 2017 10:14:31 -0500 Subject: [PATCH 145/145] Updating to v0.2 --- CHANGELOG.md | 25 +++++++++++++++++++++++++ dist/pycanvas-0.2.tar.gz | Bin 0 -> 20624 bytes 2 files changed, 25 insertions(+) create mode 100644 dist/pycanvas-0.2.tar.gz diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e48be24..7ba82783 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,31 @@ PyCanvas Changelog ================== +Version 0.2 +----------- + +**New Endpoint Coverage** + +- Groups +- Roles +- Page Revisions +- Sections +- Conversations + +**General** + +- Standardized `__str__` methods. They now (generally) follow the convention of the value of the single most relevant field followed by an ID in parentheses. +- Reworked how `requests_mock` is used in test suite. +- Nested dictionaries are now allowed as kwargs +- Split 401 into two exceptions: `InvalidAccessToken` if `'WWW-Authenticate'` header is present. Otherwise, `Unauthorized`. + + +**Bugfixes** + +- Moved some incorrectly placed enrollment methods to the Enrollment class. +- Corrected `Process` class to `Progress` +- Minor text fixes. + Version 0.1.2 ------------- diff --git a/dist/pycanvas-0.2.tar.gz b/dist/pycanvas-0.2.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..43f7eab7391c7b8edc79ad8e44b2165dfbb0d1c0 GIT binary patch literal 20624 zcmV(+QbX zM_x~*B+tt7cf3b+ymdT@XV<%llM_2Tbxt-{p+(r{h9b2jWydo$Rk!N?GxyKD=xzW6 z_)UqDos3j7u|xumuOAwXMx%TBFt+_WJKEYA>}`LyoX-yb9338h_kZ|z|9|fO8~pA5 z-~abNc9PF`yZbx)`+K`PhX+UB?d!p=EtHu@CnC_+JleH%l1*=LZKx6quu@Qtey4vFa7hH#kOO|_SWB7 z7`cHzv~~x(opXD_hL*%eN4?`9>0Hbv6FYnuS|@MMtozWOPFZMO2cZ?;GV29quwK2s z==>#^u&t?m!wKES@iZE4Z{N7_?d)nW4kp{P@pUj|{`T7k%F;QR#kWB?v@Y5BHnja4 zUZmA~@4I(UcKl!k*VYT>$D!?6KY2muI`*c>v&AO1>m}9w5B9~LOg#qWbY8h*=105^ zug@+m@uTySMdQ$&#@MF!=YKnY^YeME5>tyD*%pWsIA@cD?802!(o;tI!4V9`+dgen_ zF){X-A9%sdgON`(7E-^tII~Ws(=fQ>WQImxyx~>jWc(ZJ;vtIJgpzam4x2);e*88J zZbEx9fi$o9z|8oNLaMh9yz|YfUItTl4DT+2>-gRdnbB=SsMA$whc1gc->pASkN?Xb z+wJrJX6ygK!NLA|{eQgiAB|k!jYp%w^kMny|IWewA&vje-tz;Flbzk?htGG{>;Eb~ z-&ufSQk{LU>5SVSr*}kf#>P(L-wy3<4cqx5pm+MSc;#s zD0wxDT~B@o5oC1dvU}};>)SDNMxGnRoz8U_OhlC}kwnNOzgzxqkNIf$aXZt$V*Vc- z9`RYn=l|Zp-t$Aw`eFVb9Ifa7NCZa+&A6 zcKIy#Z&I1e`6n~Jl}}&6q%?EDbhZ?mm|VF&8-3>T31 zudcJ1&>DL|#72G)a|xJa_Yu!F0$zj}`^c91mYzZ@b7NyeeLuU#9TcSJ$t^G7^7g&$ z$B`8WykutC29>_$BBQmgm?FW4gJ*ZFd%n-&#N3D{@Vr4sB^bi*XlQ+UdHU+~^3>Y4 zr|$OMuAJ}D_E3D|{kaV_;ZNWX{FHC>O7PdNWBo(X|983569eb^ysKwp-m$nF+jfF6 z7MV+Ha2rp&?-D30=uh(NlP=q&k*R~^(gN`3 zTIy`ALZH}?NG?uOQxiH@Oik#HWE=!;xbUE+Xpbht2K?FR^X)zS+}u=Iu)XO#Cg?Zt zCuGj4FwoNGb2JTL1AI*LcBBM|f%w)_Y2nkQInnK#Pu0|T7Ks`0EGn1_&!VpN%<2g) z(U(2YSM5q#x|wD>Ij!lXs@+#j8vHZjuQzo*6f({`N1|SW_5APyG06gk^8)h#TVeNB z7zA7>6IjS$7;oQPT$Zy7pdc#{&Ce5Z*#KXeu(IYvuL?7*O7M03dQ2<1e2z8Gcdw7xGaye-^6*Yltg1JOut_rl+O4Q&n1ZX84KQ9kixP2q zA(_*hXWIB_Gi~O(91Xl1ak?cn&7E-SHBLG8tIbKL*Jo8T4~Mq03hW*dBNLR(Hap$i89l6*P5R#KyTC zUCUg2ap7|{7`vckrGcHyyx5(3%*Y&BJ(w(t0w&sunQ!y?BBCaf=dCY(Y)XWRB3_i zX`An2ZtYTSNptmT>w6gLBE;7m>QO@H;=0f!%XI?q5ukRBK%`;Kjc1YWTJ{ii5xtAcL;})1RR5;=ksQW;4nx9sz;Ae6Qv4^I zW-yyN9OAuSyUE~ohw^mvx6RC?(3_v-cZzNq6l*fPdBM0;=()bbzO=)}jKP0QB|Co3PCAXLm}Vg0G^~yt)EEvM6r^$kzSliG8ua4Scppx=Tii;Ag5}D&!7=mhL9QZMx zj*Gx?$uP9&(U&Kg6X+0sBuzC<-g zQt>M$(7_zkU|d5-_pQM99xR6w<$LvPM{;r|J)KI1wW>N_!P35j4OUGvjGA+xMfBG^ zcT8Zez7@?8Q~)&Gk|!LNj`PyWxrO)UZJ!dhD8?>LAyf?~)CaYPrB+VTGFUPd*VX#r z%%TlaX3oi%Q2Hc1i7)D!)|3Md$H6-BA5!^JqAHpzI@Z;LvRPGRkn`}&Cij^azZLeU ztF}EYlx!>XsiB zUwCuj`eoUH_4~Zpw1jg7YC>umz~SN5xa6`A9G2=V=H8A(yLsoJz(~hG_pV{^EKqC+ zBa7K(;8cvM2QmQlaj5eB0St!kx=`z9QAYyP-%9+E}EM`$Jqx&}n5}XCl z$z?~ulnQ$~K_0MxQ5bl2PYzIehhfA#mfA|80pDV>I#FznOU!405}u5LI~Inn!(Z`4 zqooQdveoZ+xh4~Az^Nc}n_D^Pl~x51peT2G(7}b8e@&Eygdh!^y__EsbF!;SSY$Q- zV%tWIqg9(P(_%9Uk2AX^nY7WBic*-gtNN!7%&3IK@D-mxzLR6!%xFu42K>a`84TNPe7RSLR=b;GOPZ+|lN>7%DBayh4Dsveqs=mWe&P=fW z$28;%8_+bxp2NV#TNzErv4g2OC|gGnLO>X>gVm1POzjUvo_Rh+BZe4?Cr ztsZEL!0ep{GoN=l{$h(VHNe@nBD@R^r#Jblhg>Z-N834S_ESL~?UUxgo!Bk%9mWLg zK813@*gY0v@cm;-XL);8m&zoSUh{7Q5?b@Z zO`~91WF*hQrmS#JV01wmd0JC7m0lW)DH631ndBLqs;{+dHj*kygN?*-%JK}2v~fyf z+Gu_Y$UZeuS`*)>?zjnq*|h$#OiF`J3pdeFLcOrWV9r7s5J9v#yeXrBZ1G5(#xGa@ zfn^Q;4F8sZ6H`ezV&Vqc5lr8rdW5>CHT{U#|031Vn18(q!Uusc@)1&ZBa98TUI-F% ziH1fkTsAkLG!j{8=`V}#9Qo)l65K`ta0mgidoo@vFm0_pum(8UQ;mR^?cUa9$#f@` zKn)sxj3`-{D;lkvk1=X>F0%(hMX)iTfHagXr3uj7EO3?BU6~Q)GB8_-^Dly=2^ps^^Buvc9*bp?x#4hv5Ba3>$=W2k`V%xVU$gDVOY|S}ZZ&85CJh>F%IJ zHBbs)cIiUlir=s7h<1y@?5{QdZ^}Dg8!q4~{6Fp;?(KvBx80+|=SO>o;Q#YzZ|C{i z|J&2||5ognEt#4MN-J^5Miuc6qSGO!}f%NdETx) zcR1$`gNK^_NRlQcUEh)mq0&>(`iw^$^OfFQl>RMeagvf8f|10|HZOQ#>rT^!>gPqj+ z|9pSF{y)w2UoGp1kYxz>jvGxq`(Xq#d~+Qq;8Etk+z78?{vYfe?(U-h_r0V2gTq}h z|M%AOfA#ZU+H8uMFSga48u#GMBw4LSKc(DQ)Frf9fOW^ynBWC>3u(wmE3kwj0MkT7($%E^?f36%lPTEL+h z7>LezA-n@XaU}irM|i6x$JdlbMlVNi6UkhO(8a}7HQc>|-g#`_xL=_M+=X=E-L{=J z)<4M3^-o&EY}M6*DDnT$UdM6yRGryg^YgtW*xDDJQYTGJA5m*=R*TN3_002l2yY8a z<*H}gJZeL^G}JV)hc|Dt>X)H$l>u-35NHH!S|_$|-{|Hxmk(1H0QA83pz#a>YencD zEoS!k7B1@Hy{Q{vR56*x4|Ejm`k6i+Sa0ASs2pqCnQ(X|rE54yYWkq4z$IMQuZ!!} zF})H4{pDRbo#!N_yQoFJ(4QEHZ3CzAEtkn|;HoKela&|oB#g8*bB@UBT@qIN*5wIc z++m;<;RLlrn;NhXrQ&Xo^~rTU^{r2^tAoGyLHPOF3-0+ok&gj9(fz0M8J~ z@qtN+_Pz?isZLvxi>iW);omBxQ;qKS%ONXxtET^}e>++L@an;G`RJ8clqtpRTq|&y z*ZHFLSG6a-{E9l4tiD>-T8nQDFJ-MPy@?hVS6)@k&6Zuy*I%Dgo-q+Uu7d}i$LE1x z?P!OX9bHj&;Uew;m=F^9#7psmnDJHj)*at+2Ifoym*l26P~hTQb*rC_nZQKvJ$exr>TSixi*b<&ng1Mxd{P0lP~58A~W8*g{`KvZpARkV38_Hf#m6PTbC(Yt9vU7=R0WMRQJIAh~1R zC8m(QJ&mbywya|8)tn_~N@jkQrj~h9h0_A?8Z77;lV}#Y5K>QGDjteCC(`EJAkJR$ zOp(C+DqE|ZftOUvf#vc%SmxSgO2?CFuS7T`wIqYKLzFC4u&uQW%TcU z#t(2(!0gE=qPWtvd{c?EYT4BWpR)zw_(B$7Q2|w`pt%I4Z>OrZEsJZB)8#il7e0pP z_0|{u>SlL~=YQH&q%pv8-~=N#W|KPp#&U*H&Um0>A)P)f%n|q>3H&5jo?}P1SAiWm z4MrGgwl|zfQy+H(p&s)N*KEpL>iRb$zV`d^oMR@5SVV+NYy&n(q@wtizvDy5*Z4%O zAzad^VYIq0h{pSA*NR++UD;uSp-r`;#ZgXCe%zr>Rc78%7lOX;jz7cJyq0S~F6WAk zj>^W{6iaVF5)w5vs9KxSm1$+{(nMEh37jfftlsD1=e#bbW&Y-_r)ADXC4iC<>sF|+ zWWuy!pqkOhoJ!&&r-8+x>S|(4v*%h4+~!mww4*p!1^C+KYnk!MXe6pM4d*(>nruu& zKyiUh@v9h|Z~@X`vF&<%3w;&LV%<}($Xx|Yk!;?Lw9IZGP(3Vkd43@xr&nV@(2^iX zXKZEgRUasmLDliaLw%u*RoyX;s`*e~@tJ}_zkmap3T8yXOYl4=WK+)`qrN2xEP|=H zu!IzZ^uysZouRlJ&8{M74PQY)GlZxC!8dWR#~>mKy7J?-=l#GO3rkVn{lxM9J-ZU2 zUwqy(*E~g)jq79b$>NLPEpbY5Q%w{oB4VWnGM{9Usg_>o>GIwj``t#oH+7*+Bs`X1 zmcp9H9<#%Sf+~gw0zoy3gOJaUw7gow+<)bADP3Kt;aM-K$35t%v42{2t~cjZfSQtF z`37ac*W#WED7I-GA6wlwf9vXe+z!XL4G-`$ip1Fzl;MZg#E!?ens-IZ3BaN&8gPL+ zj9uHK6uh^!g%5cK986MT;n{i$v$cV@Q)U5~f{Y<(KF1ppnbUap2!^;Y5!!9P+*T)FnOjRp2}{vkHEp5v;Td)=Qu7 z;8Dw;5+n6D#U476#ty9$5LiO{fx^3p=a%b417o!K1}Osb_6J~dw2^c$A|}ap+rrA9 zKbwZ^+Wo>DBRfVhkQPH-$R~!si=)NxAlFN|D`110fmYY!58YjeDZ2;%2vYl6)L#|M zLbdIaCl66S@vi)ov5V*9ENtTWH@T4%!X|Gg3WAQ44VXb0Q#6;QH`b7)!>r!UF-f27 zACqUz)W%7^MoQy6al!y?j$2dzDHm1xl0{u|~DMPFd`Mq0Ud7$;d z+y-m)SV6Jg*=TH5r=qj9v}Prh^^>bxB$ntymI>&V?cnqgQu+_0-L2Tfr9jxsfJUoh zqup;30#;v+@@#1O)+FF*MHuzK2U8n2jN(BPgf9`@ZCcy>qw@{3;wp?UWXEGr8cpo* z^N3?&WJkO@wo|5}d(G2|lVlw*5XhY895fN9w$tS<5q9aKauN9HsxabaJ3I68=GWJ> zfokp7--?EF<^#1+a=WHvtAuLF+2jgB-&|Xivj&%Gv|Q@-S>h1u#S|!GBNuIH#Wi`9 zbM;((X>B{Se(mZnx+}H7fOM223zy#LV}>7UhaGrPW3x zaQEp!LIG}x5CMa`&wQgoF0`eiyKPza@}Mb7C_p8Eh`=b9c}y>08d+Nyl`H9U+j;@3T^%$o6ON)$ zEg%sojf@yeDanm*^wC@@h0Q9*KdTXG^y=3T+<7%$<%)B0ApD3Ac!G&5E}~IXeCO>G|aaNA(92sb+w=Wj`(TDsv)++%<-6?_Y{sp+&h+jEWCat z^WtMt?Kpfe?*q8k)S)=VT|d<>Gc8Um+vSt^@3uE%r(qa`y-W(M!?OrDB+#_2JKJ*| zG13F;t;fKpzzhx?eI_@L*Ma8+_neUrEwOdVrHhIlsBXXTb+_Lg{4;RL z8V?cO<1s(IkBc^~Kf%11yL4Db1^Q`|yY6Gm!oAFF9yXCs}Z zlqT5G?o5r@G}d4M+mrlgYpnAP`u`DrpcZxmRN?>YaOe5+wEs^?zV`pKD*r#guVQzi z?2Y8_j`$6eM}7}kr}OgU@^tj}{>Bi2%PC=nkf&v4XvXlzZg?t+Y=^S2x0Y4`1CT zTw;MckRbYj1T(yz`D0xLWXuJ_AHEe{r%&W4a$R+8|z1?^(1-DsEY60+inNI2hsTS?@wn5W1km^k`(2?16kn%s#p^ zjd5Wk+?;5pvB_*<#$+hC?FbbcIJ1ce^D)RBywm^-H$hd?=2c*4k*HrXoXr$~|A>;1e{=wmfk?=QtNEz%+sb-(Z2MHDWN%R<*1@a!oz*~Gqo?X28 zYq!aTFaw|xA^u=-j*Gj!$z`u@(~|rC+#4UR)6I3?Zvsc1ZAR$tPVtF0YutFQBi!55+Hr;0Kw4k%49KPSX=;_6NhgUHPc)0nOx0+ ziY;I;JhV}hC8EhJsM6M0oksIgaleT?{E6EgH#R#iJ}j+rt6ObV)=JF6^ejx_rG&fZ z8s|1LZLAk{o!3X0Wk|p0b!_r!DoJ~ewkDFKx7=AIO39l-`F84M2kMl=yq_#hLn=^5 zQzig+t!NETmY-vooT7?@uLIBW(r7W57cnDm3FstK$hszL2HaUL7cyiPsZDjGBCMYW z7~;&9#}4Q(`uZBrL-2|4*F^5B%fNMZjVCOsXwXwp3!Hu?o4OQ--UmEyj@d{3pn7MC zGcZ+B{z3*x5v9Lhw*>**YUr;0{su^j?wB+3&Z1v%=rma?XTeqwSSWh z_QSLgKWyPxK(na_XLT~j5B{1f&<{DPo?l6otEbY=!i27>D0V4l)?;txu*!QAV&%sp zI6G4LoF+j`TX&Y8uVl#cUm|LLuUEZQ?RiQirq%RY~L~tG@tCb9WAhJv0J>SjRz%JepP6oT5mM;a6`HD@it7?>uIhlMmslv zZRf zOLM3Cx;UiIyC&W|&I3#ky}MgkCh?FSgNCISrM?zSOw8#fPI<7;5(>iqmk>M17g(`l z-jrrCgE~DHT6558xeefH&qq96i3=ZSToV5V7(3;qS*6K=Ssq`HkIRdF<_MUpO7b#u zqc5GU)^eiJJ5^unOb>08#Ux4cCtXL-`Or;Le-MoQWA`IxZp$HC<}xL}(GX25|cP{qEn~`(;=T1 z+}!Yl)qyK>=54CtEiV`;>g%P$Kys>gN^L&?Xw#6vnbZh+S}Y6jwMhLzsja_UzJ4Wb z=s`4rIM5s|379G!F?yNBn*dqwjcM$yDETblb)>O7WP6ji%zrlq&1 zgloa;WB8|`x&+Dgt!_8twLgaIUbUP~Xkh4K%o5BwmjFsx+YUG>$S&xgccJ)?s%=Gc zwXSmp((st$)5IXN#wg{gx0XIisfI0iuu7Fy`Gb`f6T~^kJVOX)DPrWVU60imKRk=E zF*T}u7URD_rtR?)9zlV09^akX@h$OP_@-PNi1tvIq`5EU$q70M*@zet;0scHqLf$W z%eU;&wX2wtNb$ulnvG!@yq;mq@syUeuXU;Z!HDGY)==){y{vvV=0&oyOM3D|uGc`S z&>)TwD8V=4vI6k~yilb>8}13mH4J-o$3ybSjd#*m-*v*7=bh~(NjiotBHj>1*`#k> z%}C=SgPtHU(qo_@3EtP4XQE(xMp6c9Tg269*AsB(iCes&{9)9x059diiED3#wEbDV zWld??#KhNC3F+N?V_{@9dWn^VO&@2qt=Kk{u-s;66xy}Se**{M)vAw*Wk!;0J{?6)V=9Vg@?G0YOg0A;H26>cPo0SciRF}nJPlS4NZ#JM zA-bmomcMq@5Os&ri1)We<#;GpKo7pwiinJzmXg(7XMUv3DW}&(TalbD+MAZpv2`V~ zxyn{cDsKyotqR|=m(qf_L_m%xjjw=zp*u5EYnw?U6xF?a$Yz=6e@YMv&H#vTbGShrje1m2+V z@h^Qcv>+BqMrl<&q4QE09P(YHA{-eYps$B|7x#VLk<*$dSa6p&uzc(1itN!;vkUbh48g_zg_zbR;SG07{BL>_-m3h z&t}b6^`re783(80*JYs#u=I6rEw?&j)rc%@8A)|-dNq+_n75YTy9yAhG)*C>H$DqQ zMYarvHKq3XfVfoHtS3wE z#C*9}5|-vq0Zrv*=mlt6VE6DuPzTH7RCCi;$M6V11p|o$A*OK@j9m&vN}+z{Rw$uW~v@F{&QVATh=faD|wJRikrU3 zD5ZL5YI^@fK}aO7q1&03`3|*efvP}Xqm4`Mah7_YueMRIQFEhyeZ{9+Rg<8ZJ*WY;HhlB(l)bUl#9#py@;F92^Zw$M~vYru0QA zYk4`s@&|~A7lK-y)iH}cnR|Krh;&z#;0-$97@@N8ac)AR@AVR;U?XM9qz0o7|8*er7`>I1D#1iFfqajy{(*Zrk zn~A!fK5{+xi(>-bE8>)Ze?xb4)HO1oeX)B`%G$P1(34lM-u!&}a&&oe@wW@kkUu1W z6#ABQvCqFHBSFvAuaCw71GdT!VmLuCNavje{2c;)W2o$2_gMsre#mJl=|lB?{j=GFTI%#LGVg3lXIxP6Qmw^^FUY@$G zLGQ)c|1hfGR(g4YSUy}S%QGrzbpZy$22GqFFTfWRRNa=kCkGjof3@7puJM2GG#%&9J@ z8=+AMml3g&p3W| zdcjzlNY*bg#|Wmd#1m!zVqHT9X!e9MndH~2@zYuV^$Qn^iU1Ys|KZNjZhHMc+Fi&0 z{3_#rrno@!Ae_dHs#X>4IZ-$NV;Tli#`jK1`~f>8hle%7TL8=CqD63RhP1FmBy805{U(7D7W3!GeR(r+ThQ+=My7xLVrI&-3k*`8gQg@D9?TL zvmmj+hh%Ex)Voy+$Y+tMKhrQSxRhc=MNuZx`K;sP1SGi-5{<5*KrxV-TadtXZaMRL z)sDF2%hwE#{~3Aq%!iO;q1Vuan#sD5kg{|U_)4`RUI>Nsx&%agr?)AOG ze0yMlxTkWhPy+wkaV3zM(ofQnKx=cX4cmeQlTXImK+CvXxpR~iqi`g}bz zK$;7_Mea$0v{(;!r#xjDu*w9$R~JZx!wZB=$_OJLKl4K_JvAdB!CabHV@F=ea2 z)RD}!#b4b}=9`1(k7kRb+Ma`ua+dX|fQMt0t&(N;rz_y<_<$ zkGx^D5p7VL!F&Es9YKVuPGSh;X#hk>-kKPKz1^BG z2D;*lFqGRf{?OJ3#Y@d9_`H~CseK#LD1j8vbNxCXZU=_NCl|bZqaXxJDwPD4^Vk~=EZ%oW8(kW z{=-$^Jb+c*wsN2$Wk@6Ty6e0%Mh!_&!JyV}3<(2sgG&JFi;q6UHjY#pWel$aS`s?b1$`8iStx7*#~eg9{05AI}O>L%kD=2CNYWOYr)igOR25DT)uv#6x7o& zfZ+f;%!W?PvU(V;Hmyz!op9dFfae5e(KH9qE3&2GOR4j2Jr@}?wS9i4HKzm;@cJ13 zDQ5R6#!k1PkZj-TcGDr-;j*V2Kfu8|vnQiyHksJMvrnZ7AEi}>)w2+6r*K8;M%IlN zT-lx_3$Unw#(KO|1-E6PZ8#zkNGj-q+`YVUFy6M?_XG_Et**I`Pb+}UJ(MQ(wY-xm z9Z0R%Wy!FQ;cMwp_uZu!(t4(~{SQVCRCKj15MB;X-k?e4QsG3>@K8$ThD~i93~mp# z+AeQfRdnFB{Jnl7jJC`^SR$sK^N;z^EKGvtMl;;Ap2BgL z20c+QZGIhaiGnJe373&WI|k&tH@9)r*f=W1l$V6GIZw^nN80<##kDmnM3ioVtL2>E z7&xl%ynEgg@Hi6^p{lnnm7%6TnxOl4dI)Sp(5Uq+3( zRJLAw45^qEYwH)%(YK+QuU<*ow|#3LA`2PC)#vlfapItStvH<^ErGk9POc{=c8^@2&lRuKm9>@c;4}5xWu{S^fKf%4Gz2 zJff$%g}Qttu_R<4GzvJ9wP{t~h+fsbW-V1o zC~5T|=Z3RDWoFIPD9@u^sQ^@UETPq4s?puWWq_(Om`!6`J#`3$%nQb!%dKo97K_}L zl*W;R)Aa{8l-C`=GyG?;p^qtS4tRy<5F5`t{yUjfJkP|svLgzPr5o%D_e2!bCTZA- zg9c7o*+xqAg73?K#p2#Mh9nf;>Ob=u%^XOoNP-_KzUdLe36*#dE^dYG)65r*nUQBu zoI$n-CLLki!vgcC+RS;I`hJP?i3)i7JX-X`H!`KjwwYeJF`G0|l|e8RVYY`^>55P( z^R93}s{ki!4|G>}G0F}kqnbTVAh z=qY(p$M{LP{ITWI068IW&)LfsQMxpNLqlrTGP)LzEQ8wOR?Hu+%yDumZsHxc4~>xYp3?R zMr^Le{yU8-nod01^b$(lwM-|W?JSJn76&ubQJAaM@W|kJv~8VKF=KpO#gxVhbG1`C zf1(6S;!n%lC{+spYR#En%k}h^`*{>!g{WjBNDXG$tN1^t5u&UqVTyX%y@jy1zWgu_U{U zVAB{jr!?4f=8o*+gqp7Ce|o>HFw^M~XgA1o0nAJ9ZslHrsKI_r5l7sXC&>m0e{->= z2^YDTse8-VfaMC}N@K_=G+2vA;0Y7V&1y%K&X0lwzlowHDb8Uck*u`al1j8K$Sy1N zmMo}#@Hf>!;H>Y&kL8(3qy)fB&#XWq%vsRi>9}bS&s^4iboC(D^uB^jBdeS|2uea= zq(j0$TPPfAbzCtP$-Y4=pN{uP(9!o-$;>ki8jP&EaFd)DzDRW>F55ZuzKVGv*nIx2 zH+_J5KU~{?JVpBtUF!Qg2S}?DpS1eZxGb>O^(=ja`LAnJ7Gwey^Z)q{pLH<* z`QM%0!yTCaM@I+i`M-*fnByVSXSSx*3gpDrCAX6+KDX_099f?PQZTgUwO|zxA+ffq z1!55n5-I}`c5k2x)HsUkyRl#?eR&=-!dmw&LzKaGIT!voX;@l;)g#}J@ zd>;7uF-S=|8Zl3}q&P;+YfqE!L<&_ZY6rykbRwgsJfZPBomf?tCsg0_6QRNh2vYq) z2LJW9vZNone4mC|EWGDP*(i>mdeOIbcXl?@RD58|6_vs?s!ln@XS!D(44%dOo!c67 zXp>GC4|K5bW}!{;NQIGi*cWY#dEbLxQoDJIDf$-w3mON{9kb2Nf=jAKQZMjtHdFgR zQD4}V@vT}Mj4%h~+brC%Cy@JqDLdpNv1Y4zye1D7x=%qTsXZQL%qQra`bl)?(AypeaTlUVaSsksVA)A&69sh3d zHdOq;mg!marwz5yOHEh6wLM{@k=~(hH1hb=;hoBN&wc^EM4=v&nl#m(P3upT8Y9}K zdM^cG13mc#(J17W^Y6!^AhKZ?k?k9 zQVmt>{EBII%nrx5J*3}HYW1PZQ*=LWlIo|oy9s!rva?*2>Drf~r>YF1l z#kUN_dHP1{TkfKTCi=%JeO?j_4lU9kB?=b}3~eVreK1WW52pDESvn!ZkcPXRblY+l zH5^N1dB;XwjReFoVUU%S;=9o;b9!msnpWpg=M2&-KvA}2tZe|rZz&v%|b-vj@@ z{IlNwKWY7+^atL0l*ib<5xT$oApCso1@|NLK$++Egs+vgV#zgr=e5 ziVP^yH>CvSg4O(y4`l6tP;Y)W7qa~_xAQBJ+hF>VqWIg)?-jM8a+SbjFd{4Q)goK; zY-#S`poA*pJe6in-xmnr%#xz0=DV?~grI(1w+MpO*L7Ee!baD2+lE->mEGS7%2vAm zCxfzl`;Xn7odfj$vA=h;e{i@<_8$l9^?$YJzc}E0*{%xkLeuj{+j&P>`a(W9l_~qy z8UO9M&dGSprSZ$)Gvm+Rv52F8%w7g8IuGL0FQAA-Qty3x##vA3{=%G2VpjZ4LNNab z`W?+f_sV0a+`Z-dIuF7M{N?iUt!hs7DY8^x_bnT%JBp#c1x#C*_{%)B1j8@u{kvDv zj#!+5%cH_B9*3t*{Sy+p-KwCk*M!P|19kA~3fCrhXE8r^AKsy{vxe3ioQ1YWRTPhbUcH$b1F%OhnCq<{sYNH)AB1t+wxg22m^46{WQ$_IRgr!~A_-YUl&VVfr8 zERI}K-<0X zgHX^5e2?zz!jw}jk@ktMsj#E5=h`#x+j9`BSg4})cuj!A%CaKaRyKREw6MbZfVrb z>Pt(JW71`%&q@B3n35#t*aAtWrL{%~Z#h*`ng&rdDIwlg5~ICEb6TPrN*Iig8Xz#b8Au2t6p4u-Au0_6!O=)eFzFo91EfcXbTjD?q;r(ufNg)y zxvq1r^Ywgszd!Gn=lVVO??!*vB(pQ^jr4$iFCB#1)ZB+1G-%8CQ?eWpTAjZa+9e^X z-Z40}VA1*n5aJ0m%*LJ460&~$I&NeQq#MM#Ura_D@s1*3C-kvir{~+&Zk;aCR8UT+aflp^x%8qpi*PgMx4WPUXVuKEb{%Vp72fN$ffVKf%$+EIPH-~3>`ua3ZGl(GH<5ax;Qh-a1G7PSV3>g>d0~ZIW z@mi+jya(KR&C(?g<{OWZQ{x-=x&_gmJ)Ve)GGySpDkAyre-#1%Pd;5{(AWikvSBV%ZC+jsvL=JFuQPT^t-ADUN71Q5rT|%Y~89iXR!$Vt1iN&G&T==ty*}8#^zptX(RYosa{Sm$Ob=01^ zSNJG!Ux_@_m9etzT~-hvMxsQudKjX)qosPB)sgLPvLZid|GSs>6!{Q~381x_X}`|2 zErQ58vgQvLe`_OG+Mp1xn!lIeNLhC>0MK6i^@+`9v3}z8(9XH(VZQy2)?}~h>PJDl zoz7Du9Leb_xyq;&Qx`o&&^AIKA{Pl8SMrA)!f%E>wXpa#2ogE_R$KpLBu(?$AcJp# zO;jGirba(XZpTD{8ZUZg)_;-PYap}@*#CUM_5yZ9zbzMeD(n_}g}XY**IAL`W8vZ* zn1^bGoIJ+Iny%k+3dPS0&!>}wHN5Ud0+zMAKe(^iLH+8JyF^m2_yR}PKL%~C8KelgZ3}@ zT+M}p;xr8WX(upTkxQOGj$$bRuletBA|J;C+>u93#U+v6HU2dWDVHwJ9Bbt1X!3IO zQFao|493Hlh2?E=Sz+h&;0Pb4wQzUJaXT7Sxw(*{+WXTXHKC^>>u?pkS@hWe-x=En z7Ei&$(`U(aDFCqdu3XUz`~e&7IhDE3G7RLXsp3UomfXA%VwQYkpjh+crs`2+#dpdX zeElNfyQl-2U-MNDQ(EtUp*T%kJ=~=DkB+m1Lp!aRVa3L+*c{;gxeV^4(=$b=oN z>s)b_R0zc4ZXS^qiNo#hwu!jqv9WU^t}cM6C0eK_;?G)h#uw~it;C>d!e8>5j@GS2 ze(=CWcq0tZFnx{k*#T0E4#UQ`;xiWbKs;!bcT;^^p++`Q);ouLApXf!JN`160a!~J z2fNv=m~#tW{ID)lRq(9CmTDR)rQo>7RAKnSE5|}B4IB!GZhO{XHA>d zLa1+!z*mH5D9-BklMRP*;p=T%SUrSVA6o6_$6rzrEGd9&`Sb zwm@GuWZy+rcDIU+TEsvhR9>N9c}Gmwx?MfT6UJFbt%{)L)4u-yOP~?F4MG3sn;IT zF!j{FnO71_9|?}BA*Z#S5o)H$>Vrr^!?Ww_CM{+oks~sv)qGR@E>q(zC1jwAKe8nK`Cs#QqAghz3A$8LvOL>bP>1hVulU*tz2Bh< zv*W(+-<1Dyzx|E|3OQW&%=8%D7P6i9u9ELB2c8C^cuiPweN?RD{!3{xwKp;5HG>-tmAd>*p<2G2e3uaXX5 zEAOsk7b=oYatEK&aqlK_p2{g)&h z4!7rcNh&8GO4q{|DHz}(0a-iL-(&03fmUispZCK}=F%C^xzy`)L2NX^O7%NT{6C?I zrauk-m>}I)@>U#904hBK^@CgE0UCcklv6U3L<@TO8 z?ZJWq(>*Wq_F2KJ!_vGdM>6G9=`{A;ye~*+Osb=@-vKo~L0n)HAl> zS*?O!qRe@hgUsS{WuSH3dDg=?^kWUs*Sc8`te|GApZUf91t@k@`|y4p(n<2j2NV2H zMFILE#?1`ehaDYFa(IoI1>Usal3g|7HAXeHn(Ynm1ygkq^2rN^KD9_><4dw-BAya- zsm%3M??DQ%kSNEUQZ=SK5h=W1nOQ~2%0Qr1Xu zI(UB@x;(bskbdSGSd&_jeEoYE4Mv|#6geFe%LBzraWS5Q4f^3!-v-YR1%LMDn!OoYar2kNt+S{)|rJnryFFpc@} zA?}H-ToO<3Un839(JX)+k4!;;{I+3nJwS_#1|WaG%$+l><$jS7?=@!Wx6IYZhieBE zsIVwObB!yQmEhfVhREWGF`v|mLd2{T*Xn_O*0 zb#~sxvvdLLNv*9{l*p1gac)8Fa#+!30Fj_VWDb8{Gsv$NFYMxR34CKsjOM<*{tvge zx3^oqMzRQ+Wbz&&8LYSR|Fmwc$D(^%oCan|LdOW_v-jR)swDqYmZ>wfsbd5oU>8q1 zbw?~JDv`E^r2irs8zHWEoQpeRkK_u>ni8vRA33{nZqYd>X}S4V%qjGU*F8Q z-S9B)M76rWjI-E*pUq)oVkd4G7Dhi8W}T{KaJ7>Emt~8A#lvaE&11ESa2i_aBg`{6 z+Tge{H+t%RyV4NXj9b*0Ii%dZ0RyQqzaBa7W7M4bA+M7uF89M$vS=ScSMo}@;JBYx z#;5P-e0Q8(O;OV70kf^SIurW;%C(-+rYQ%I7pN{RWjx*iHaggWbY4$VM<(zwqgC7n z0&i8KIot!?vO*y`U@zqq1qrScKpra|eviou{#k!%P`L_}_(Fzdlv@A1=#7zM6NPE4juRU6td@9%iO%=Y_l@^8Z0+T#`1_9M z^JM&Kwk`0P_1_kWR9b658Dm^N9xF2E-($(^>4J}(SFw`ZUtC{LBxUlK?IA1`iEemz z6;ho(H#h8ewYBcrrP}PHEfO;`v)@$#|3(%|`b0v=1Y-B+>CooO^}uHo(P{^Qt3PVO z)pC^(*DIACQ~CeiDplx~S0q_iU7!_6XMX-&M0aN#zJ2-5Ac;WuOLPbFdn(;=Tn)Ry zMmC4iTeNNy${0TLFc@t>g_8|r8I?VmZXYn)OlVE33*HTjChq6IfK1kC$+>(YtDolk z@AR$@9bIwJ3c2_3`Ga%FNv9Nv)T!2Ri~lV@QT`Hd&w+4F;x5b3=zUdCun@Hz0c%D0 z2App1URG=f8LUp}1;YkTN~Wp~qyc z-F)wfZ)jm=ub&w4G%}+~F#x>(jX#@CdTFlFdWB3!prCi9XMv_iSa+C>FJAyT#$u+e znPANPp>b9!3@%&KlZDR*D-sh=x*>tU^o4lsPi(r$I#bJkWV50(tGDARBh+#AOBxZ!ZDu2Rhu70R!BkiAC+|&qyPW_ literal 0 HcmV?d00001