From 099c06d672d17dde8cd716aadd67c374c4be9851 Mon Sep 17 00:00:00 2001 From: Jen Li Date: Fri, 26 Jun 2015 15:08:05 -0700 Subject: [PATCH 01/42] porting to python --- .../test_task_router_capability.py | 253 ++++++++++++++++++ .../test_task_router_taskqueue_capability.py | 132 +++++++++ .../test_task_router_worker_capability.py | 162 +++++++++++ .../test_task_router_workspace_capability.py | 131 +++++++++ twilio/task_router/__init__.py | 144 ---------- twilio/task_router/capability/__init__.py | 10 + .../task_router/capability/capability_api.py | 68 +++++ .../capability/task_router_capability.py | 184 +++++++++++++ 8 files changed, 940 insertions(+), 144 deletions(-) create mode 100644 tests/task_router/test_task_router_capability.py create mode 100644 tests/task_router/test_task_router_taskqueue_capability.py create mode 100644 tests/task_router/test_task_router_worker_capability.py create mode 100644 tests/task_router/test_task_router_workspace_capability.py delete mode 100644 twilio/task_router/__init__.py create mode 100644 twilio/task_router/capability/__init__.py create mode 100644 twilio/task_router/capability/capability_api.py create mode 100644 twilio/task_router/capability/task_router_capability.py diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py new file mode 100644 index 0000000000..35b1cb7678 --- /dev/null +++ b/tests/task_router/test_task_router_capability.py @@ -0,0 +1,253 @@ +import sys + +import time +import unittest +import warnings + +from twilio import jwt +from twilio.task_router.capability import TaskRouterCapability + +class TaskRouterCapabilityTest(unittest.TestCase): + + def test_workspace_default(self): + account_sid = "AC123" + auth_token = "foobar" + workspace_sid = "WS456" + channel_id = "WS456" + capability = TaskRouterCapability(account_sid, auth_token, workspace_sid, channel_id) + + capability.generate_token() + + token = capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(decoded["iss"], account_sid) + self.assertEqual(decoded["account_sid"], account_sid) + self.assertEqual(decoded["workspace_sid"], workspace_sid) + self.assertEqual(decoded["channel"], channel_id) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], channel_id) + + policies = decoded['policies'] + self.assertEqual(len(policies), 3) + + # websocket GET + get_policy = policies[0] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", get_policy['url']) + self.assertEqual("GET", get_policy['method']) + self.assertTrue(get_policy['allowed']) + self.assertEqual({}, get_policy['query_filter']) + self.assertEqual({}, get_policy['post_filter']) + + # websocket POST + post_policy = policies[1] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", post_policy['url']) + self.assertEqual("POST", post_policy['method']) + self.assertTrue(post_policy['allowed']) + self.assertEqual({}, post_policy['query_filter']) + self.assertEqual({}, post_policy['post_filter']) + + # fetch GET + fetch_policy = policies[2] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456", fetch_policy['url']) + self.assertEqual("GET", fetch_policy['method']) + self.assertTrue(fetch_policy['allowed']) + self.assertEqual({}, fetch_policy['query_filter']) + self.assertEqual({}, fetch_policy['post_filter']) + + def test_worker_default(self): + account_sid = "AC123" + auth_token = "foobar" + workspace_sid = "WS456" + worker_sid = "WK789" + capability = TaskRouterCapability(account_sid, auth_token, workspace_sid, worker_sid) + + capability.generate_token() + + token = capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(decoded["iss"], account_sid) + self.assertEqual(decoded["account_sid"], account_sid) + self.assertEqual(decoded["workspace_sid"], workspace_sid) + self.assertEqual(decoded["worker_sid"], worker_sid) + self.assertEqual(decoded["channel"], worker_sid) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], worker_sid) + + policies = decoded['policies'] + self.assertEqual(len(policies), 4) + + # activity GET + fetch_activity = policies[0] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", fetch_activity['url']) + self.assertEqual("GET", fetch_activity['method']) + self.assertTrue(fetch_activity['allowed']) + self.assertEqual({}, fetch_activity['query_filter']) + self.assertEqual({}, fetch_activity['post_filter']) + + # websocket GET + get_policy = policies[1] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", get_policy['url']) + self.assertEqual("GET", get_policy['method']) + self.assertTrue(get_policy['allowed']) + self.assertEqual({}, get_policy['query_filter']) + self.assertEqual({}, get_policy['post_filter']) + + # websocket POST + post_policy = policies[2] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", post_policy['url']) + self.assertEqual("POST", post_policy['method']) + self.assertTrue(post_policy['allowed']) + self.assertEqual({}, post_policy['query_filter']) + self.assertEqual({}, post_policy['post_filter']) + + # fetch GET + fetch_policy = policies[3] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", fetch_policy['url']) + self.assertEqual("GET", fetch_policy['method']) + self.assertTrue(fetch_policy['allowed']) + self.assertEqual({}, fetch_policy['query_filter']) + self.assertEqual({}, fetch_policy['post_filter']) + + def test_task_queue_default(self): + account_sid = "AC123" + auth_token = "foobar" + workspace_sid = "WS456" + taskqueue_sid = "WQ789" + capability = TaskRouterCapability(account_sid, auth_token, workspace_sid, taskqueue_sid) + + capability.generate_token() + + token = capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(decoded["iss"], account_sid) + self.assertEqual(decoded["account_sid"], account_sid) + self.assertEqual(decoded["workspace_sid"], workspace_sid) + self.assertEqual(decoded["taskqueue_sid"], taskqueue_sid) + self.assertEqual(decoded["channel"], taskqueue_sid) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], taskqueue_sid) + + policies = decoded['policies'] + self.assertEqual(len(policies), 3) + + # websocket GET + get_policy = policies[0] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", get_policy['url']) + self.assertEqual("GET", get_policy['method']) + self.assertTrue(get_policy['allowed']) + self.assertEqual({}, get_policy['query_filter']) + self.assertEqual({}, get_policy['post_filter']) + + # websocket POST + post_policy = policies[1] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", post_policy['url']) + self.assertEqual("POST", post_policy['method']) + self.assertTrue(post_policy['allowed']) + self.assertEqual({}, post_policy['query_filter']) + self.assertEqual({}, post_policy['post_filter']) + + # fetch GET + fetch_policy = policies[2] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789", fetch_policy['url']) + self.assertEqual("GET", fetch_policy['method']) + self.assertTrue(fetch_policy['allowed']) + self.assertEqual({}, fetch_policy['query_filter']) + self.assertEqual({}, fetch_policy['post_filter']) + + def test_deprecated_worker(self): + account_sid = "AC123" + auth_token = "foobar" + workspace_sid = "WS456" + worker_sid = "WK789" + capability = TaskRouterCapability(account_sid, auth_token, workspace_sid, worker_sid) + + capability.generate_token() + + token = capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(decoded["iss"], account_sid) + self.assertEqual(decoded["account_sid"], account_sid) + self.assertEqual(decoded["workspace_sid"], workspace_sid) + self.assertEqual(decoded["worker_sid"], worker_sid) + self.assertEqual(decoded["channel"], worker_sid) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], worker_sid) + + policies = decoded['policies'] + self.assertEqual(len(policies), 4) + + # should expect 4 policies + + # activity GET + fetch_activity = policies[0] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", fetch_activity['url']) + self.assertEqual("GET", fetch_activity['method']) + self.assertTrue(fetch_activity['allowed']) + self.assertEqual({}, fetch_activity['query_filter']) + self.assertEqual({}, fetch_activity['post_filter']) + + # websocket GET + get_policy = policies[1] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", get_policy['url']) + self.assertEqual("GET", get_policy['method']) + self.assertTrue(get_policy['allowed']) + self.assertEqual({}, get_policy['query_filter']) + self.assertEqual({}, get_policy['post_filter']) + + # websocket POST + post_policy = policies[2] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", post_policy['url']) + self.assertEqual("POST", post_policy['method']) + self.assertTrue(post_policy['allowed']) + self.assertEqual({}, post_policy['query_filter']) + self.assertEqual({}, post_policy['post_filter']) + + # fetch GET + fetch_policy = policies[3] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", fetch_policy['url']) + self.assertEqual("GET", fetch_policy['method']) + self.assertTrue(fetch_policy['allowed']) + self.assertEqual({}, fetch_policy['query_filter']) + self.assertEqual({}, fetch_policy['post_filter']) + + # check deprecated warnings + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + capability.allow_worker_fetch_attributes() + assert len(w) == 1 + assert issubclass(w[-1].category, DeprecationWarning) + assert "deprecated" in str(w[-1].message) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + capability.allow_worker_activity_updates() + assert len(w) == 1 + assert issubclass(w[-1].category, DeprecationWarning) + assert "deprecated" in str(w[-1].message) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + capability.allow_task_reservation_updates() + assert len(w) == 1 + assert issubclass(w[-1].category, DeprecationWarning) + assert "deprecated" in str(w[-1].message) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/tests/task_router/test_task_router_taskqueue_capability.py b/tests/task_router/test_task_router_taskqueue_capability.py new file mode 100644 index 0000000000..bd3cec6295 --- /dev/null +++ b/tests/task_router/test_task_router_taskqueue_capability.py @@ -0,0 +1,132 @@ +import sys +import time +import unittest + +from twilio import jwt +from twilio.task_router.capability import TaskRouterTaskQueueCapability + +class TaskRouterTaskQueueCapabilityTest(unittest.TestCase): + + def setUp(self): + self.account_sid = "AC123" + self.auth_token = "foobar" + self.workspace_sid = "WS456" + self.taskqueue_sid = "WQ789" + self.capability = TaskRouterTaskQueueCapability(self.account_sid, self.auth_token, self.workspace_sid, self.taskqueue_sid) + + def test_generate_token(self): + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(decoded["iss"], self.account_sid) + self.assertEqual(decoded["account_sid"], self.account_sid) + self.assertEqual(decoded["workspace_sid"], self.workspace_sid) + self.assertEqual(decoded["taskqueue_sid"], self.taskqueue_sid) + self.assertEqual(decoded["channel"], self.taskqueue_sid) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], self.taskqueue_sid) + + def test_generate_token_with_default_ttl(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(int(time.time()) + 3600, decoded["exp"]) + + def test_generate_token_with_custom_ttl(self): + ttl = 10000 + + token = self.capability.generate_token(ttl) + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(int(time.time()) + 10000, decoded["exp"]) + + def test_default(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 3) + + # websocket GET + get_policy = policies[0] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", get_policy['url']) + self.assertEqual("GET", get_policy['method']) + self.assertTrue(get_policy['allowed']) + self.assertEqual({}, get_policy['query_filter']) + self.assertEqual({}, get_policy['post_filter']) + + # websocket POST + post_policy = policies[1] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", post_policy['url']) + self.assertEqual("POST", post_policy['method']) + self.assertTrue(post_policy['allowed']) + self.assertEqual({}, post_policy['query_filter']) + self.assertEqual({}, post_policy['post_filter']) + + # fetch GET + fetch_policy = policies[2] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789", fetch_policy['url']) + self.assertEqual("GET", fetch_policy['method']) + self.assertTrue(fetch_policy['allowed']) + self.assertEqual({}, fetch_policy['query_filter']) + self.assertEqual({}, fetch_policy['post_filter']) + + def test_allow_fetch_subresources(self): + self.capability.allow_fetch_subresources() + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 4) + + # confirm the additional policy generated with allow_fetch_subresources() + + policy = policies[3] + + self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789/**") + self.assertEqual(policy['method'], "GET") + self.assertTrue(policy['allowed']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) + + def test_allow_updates_subresources(self): + self.capability.allow_updates_subresources() + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 4) + + # confirm the additional policy generated with allow_updates_subresources() + + policy = policies[3] + + self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789/**") + self.assertEqual(policy['method'], "POST") + self.assertTrue(policy['allowed']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py new file mode 100644 index 0000000000..030190747d --- /dev/null +++ b/tests/task_router/test_task_router_worker_capability.py @@ -0,0 +1,162 @@ +import sys + +import time +import unittest +import warnings + +from twilio import jwt +from twilio.task_router.capability import TaskRouterWorkerCapability + +class TaskRouterWorkerCapabilityTest(unittest.TestCase): + + def setUp(self): + self.account_sid = "AC123" + self.auth_token = "foobar" + self.workspace_sid = "WS456" + self.worker_sid = "WK789" + self.capability = TaskRouterWorkerCapability(self.account_sid, self.auth_token, self.workspace_sid, self.worker_sid) + + def test_generate_token(self): + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(decoded["iss"], self.account_sid) + self.assertEqual(decoded["account_sid"], self.account_sid) + self.assertEqual(decoded["workspace_sid"], self.workspace_sid) + self.assertEqual(decoded["worker_sid"], self.worker_sid) + self.assertEqual(decoded["channel"], self.worker_sid) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], self.worker_sid) + + def test_generate_token_with_default_ttl(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(int(time.time()) + 3600, decoded["exp"]) + + def test_generate_token_with_custom_ttl(self): + ttl = 10000 + + token = self.capability.generate_token(ttl) + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(int(time.time()) + 10000, decoded["exp"]) + + def test_defaults(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + websocket_url = 'https://event-bridge.twilio.com/v1/wschannels/%s/%s' % (self.account_sid, self.worker_sid) + + # expect 5 policies + policies = decoded['policies'] + self.assertEqual(len(policies), 5) + + # policy 0 - GET websocket + get_policy = policies[0] + self.assertIsNotNone(get_policy) + self.assertEqual(get_policy['url'], websocket_url) + self.assertEqual(get_policy['method'], 'GET') + self.assertTrue(get_policy['allowed']) + self.assertEqual(get_policy['query_filter'], {}) + self.assertEqual(get_policy['post_filter'], {}) + + # policy 1 - POST + post_policy = policies[1] + self.assertIsNotNone(post_policy) + self.assertEqual(post_policy['url'], websocket_url) + self.assertEqual(post_policy['method'], 'POST') + self.assertTrue(post_policy['allowed']) + self.assertEqual(post_policy['query_filter'], {}) + self.assertEqual(post_policy['post_filter'], {}) + + # policy 2 - Worker fetch + worker_fetch_policy = policies[2] + self.assertIsNotNone(worker_fetch_policy) + self.assertEqual(worker_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789') + self.assertEqual(worker_fetch_policy['method'], 'GET') + self.assertTrue(worker_fetch_policy['allowed']) + self.assertEqual(worker_fetch_policy['query_filter'], {}) + self.assertEqual(worker_fetch_policy['post_filter'], {}) + + # policy 3 - Reservation fetch + reservation_fetch_policy = policies[3] + self.assertIsNotNone(reservation_fetch_policy) + self.assertEqual(reservation_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**') + self.assertEqual(reservation_fetch_policy['method'], 'GET') + self.assertTrue(reservation_fetch_policy['allowed']) + self.assertEqual(reservation_fetch_policy['query_filter'], {}) + self.assertEqual(reservation_fetch_policy['post_filter'], {}) + + # policy 4 - Activity fetch + activity_fetch_policy = policies[4] + self.assertIsNotNone(activity_fetch_policy) + self.assertEqual(activity_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities') + self.assertEqual(activity_fetch_policy['method'], 'GET') + self.assertTrue(activity_fetch_policy['allowed']) + self.assertEqual(activity_fetch_policy['query_filter'], {}) + self.assertEqual(activity_fetch_policy['post_filter'], {}) + + def test_allow_activity_updates(self): + + # allow activity updates to the worker + self.capability.allow_activity_updates() + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 6) + policy = policies[5] + + url = "https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s" % (self.workspace_sid, self.worker_sid) + + self.assertEqual(url, policy["url"]) + self.assertEqual("POST", policy["method"]) + self.assertTrue(policy["allowed"]) + self.assertIsNotNone(policy['post_filter']) + self.assertEqual({}, policy['query_filter']) + self.assertTrue(policy['post_filter']['ActivitySid']) + + def test_allow_reservation_updates(self): + # allow reservation updates + self.capability.allow_reservation_updates() + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 6) + + policy = policies[5] + + url = "https://taskrouter.twilio.com/v1/Workspaces/%s/Tasks/**" % self.workspace_sid + + self.assertEqual(url, policy["url"]) + self.assertEqual("POST", policy["method"]) + self.assertTrue(policy["allowed"]) + self.assertIsNotNone(policy["post_filter"]) + self.assertEqual({}, policy["query_filter"]) + self.assertTrue(policy["post_filter"]["ReservationStatus"]) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/task_router/test_task_router_workspace_capability.py b/tests/task_router/test_task_router_workspace_capability.py new file mode 100644 index 0000000000..cc389f7120 --- /dev/null +++ b/tests/task_router/test_task_router_workspace_capability.py @@ -0,0 +1,131 @@ +import sys + +import time +import unittest + +from twilio import jwt +from twilio.task_router.capability import TaskRouterWorkspaceCapability + +class TaskRouterWorkspaceCapabilityTest(unittest.TestCase): + + def setUp(self): + self.account_sid = "AC123" + self.auth_token = "foobar" + self.workspace_sid = "WS456" + self.capability = TaskRouterWorkspaceCapability(self.account_sid, self.auth_token, self.workspace_sid) + + def test_generate_token(self): + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(decoded["iss"], self.account_sid) + self.assertEqual(decoded["account_sid"], self.account_sid) + self.assertEqual(decoded["workspace_sid"], self.workspace_sid) + self.assertEqual(decoded["channel"], self.workspace_sid) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], self.workspace_sid) + + def test_generate_token_with_default_ttl(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(int(time.time()) + 3600, decoded["exp"]) + + def test_generate_token_with_custom_ttl(self): + ttl = 10000 + + token = self.capability.generate_token(ttl) + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(int(time.time()) + 10000, decoded["exp"]) + + def test_default(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 3) + + # websocket GET + get_policy = policies[0] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", get_policy['url']) + self.assertEqual("GET", get_policy['method']) + self.assertTrue(get_policy['allowed']) + self.assertEqual({}, get_policy['query_filter']) + self.assertEqual({}, get_policy['post_filter']) + + # websocket POST + post_policy = policies[1] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", post_policy['url']) + self.assertEqual("POST", post_policy['method']) + self.assertTrue(post_policy['allowed']) + self.assertEqual({}, post_policy['query_filter']) + self.assertEqual({}, post_policy['post_filter']) + + # fetch GET + fetch_policy = policies[2] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456", fetch_policy['url']) + self.assertEqual("GET", fetch_policy['method']) + self.assertTrue(fetch_policy['allowed']) + self.assertEqual({}, fetch_policy['query_filter']) + self.assertEqual({}, fetch_policy['post_filter']) + + def test_allow_fetch_subresources(self): + self.capability.allow_fetch_subresources() + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 4) + + # confirm the additional policy generated with allow_fetch_subresources() + + policy = policies[3] + + self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/**") + self.assertEqual(policy['method'], "GET") + self.assertTrue(policy['allowed']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) + + def test_allow_updates_subresources(self): + self.capability.allow_updates_subresources() + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 4) + + # confirm the additional policy generated with allow_updates_subresources() + + policy = policies[3] + + self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/**") + self.assertEqual(policy['method'], "POST") + self.assertTrue(policy['allowed']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py deleted file mode 100644 index 17975c1cba..0000000000 --- a/twilio/task_router/__init__.py +++ /dev/null @@ -1,144 +0,0 @@ -import time - -from .. import jwt - - -TASK_ROUTER_BASE_URL = 'https://taskrouter.twilio.com' -TASK_ROUTER_BASE_WS_URL = 'https://event-bridge.twilio.com/v1/wschannels' - -REQUIRED = {'required': True} -OPTIONAL = {'required': False} - - -class TaskRouterCapability(object): - """ - A token to control permissions for the TaskRouter service. - - :param str account_sid: The account to generate a token for - :param str auth_token: The auth token for the account. Used to sign the - token and will not be included in generated output. - :param str workspace_sid: The workspace to grant capabilities over - :param str worker_sid: The worker sid to grant capabilities over - :param str base_url: The base TaskRouter API URL - :param str base_ws_url: The base TaskRouter event stream URL - """ - def __init__(self, account_sid, auth_token, workspace_sid, worker_sid, - base_url=TASK_ROUTER_BASE_URL, - version='v1', - base_ws_url=TASK_ROUTER_BASE_WS_URL): - self.account_sid = account_sid - self.auth_token = auth_token - self.workspace_sid = workspace_sid - self.worker_sid = worker_sid - self.version = version - self.base_url = '%s/%s' % (base_url, self.version) - self.base_ws_url = base_ws_url - self.policies = [] - - self._allow_worker_websocket_urls() - self._allow_activity_list_fetch() - - @property - def workspace_url(self): - return '%s/Workspaces/%s' % (self.base_url, self.workspace_sid) - - @property - def worker_url(self): - return '%s/Workers/%s' % (self.workspace_url, self.worker_sid) - - def _allow_worker_websocket_urls(self): - worker_event_url = '%s/%s/%s' % ( - self.base_ws_url, - self.account_sid, - self.worker_sid, - ) - self.policies.append(make_policy( - worker_event_url, - 'GET', - )) - self.policies.append(make_policy( - worker_event_url, - 'POST', - )) - - def _allow_activity_list_fetch(self): - self.policies.append(make_policy( - '%s/Activities' % self.workspace_url, - 'GET', - )) - - def allow_worker_activity_updates(self): - self.policies.append(make_policy( - self.worker_url, - 'POST', - post_filter={'ActivitySid': REQUIRED}, - )) - - def allow_worker_fetch_attributes(self): - self.policies.append(make_policy( - self.worker_url, - 'GET', - )) - - def allow_task_reservation_updates(self): - tasks_url = '%s/Tasks/**' % self.workspace_url - self.policies.append(make_policy( - tasks_url, - 'POST', - post_filter={'ReservationStatus': REQUIRED}, - )) - - def generate_token(self, ttl=3600, attributes=None): - """ - Generate a token string based on the credentials and permissions - previously configured on this object. - - :param int ttl: Expiration time in seconds of the token. Defaults to - 3600 seconds (1 hour). - :param dict attributes: Extra attributes to pass into the token. - """ - - return self._generate_token( - ttl, - { - 'account_sid': self.account_sid, - 'channel': self.worker_sid, - 'worker_sid': self.worker_sid, - 'workspace_sid': self.workspace_sid, - } - ) - - def _generate_token(self, ttl, attributes=None): - payload = { - 'version': self.version, - 'friendly_name': self.worker_sid, - 'policies': self.policies, - 'iss': self.account_sid, - 'exp': int(time.time()) + ttl, - } - - if attributes is not None: - payload.update(attributes) - - return jwt.encode(payload, self.auth_token, 'HS256') - - -def make_policy(url, method, query_filter=None, post_filter=None, - allowed=True): - """ - Create a policy dictionary for the given resource and method. - - :param str url: the resource URL to grant or deny access to - :param str method: the HTTP method to allow or deny - :param dict query_filter: specific GET parameter names to require or allow - :param dict post_filter: POST parameter names to require or allow - :param allowed bool: whether this request is allowed - """ - - return { - 'url': url, - 'method': method, - 'allow': allowed, - 'query_filter': query_filter or {}, - 'post_filter': post_filter or {}, - } diff --git a/twilio/task_router/capability/__init__.py b/twilio/task_router/capability/__init__.py new file mode 100644 index 0000000000..73e66978ef --- /dev/null +++ b/twilio/task_router/capability/__init__.py @@ -0,0 +1,10 @@ +from .capability_api import ( + CapabilityAPI +) + +from .task_router_capability import ( + TaskRouterCapability, + TaskRouterWorkerCapability, + TaskRouterTaskQueueCapability, + TaskRouterWorkspaceCapability +) diff --git a/twilio/task_router/capability/capability_api.py b/twilio/task_router/capability/capability_api.py new file mode 100644 index 0000000000..af1a9bf4ed --- /dev/null +++ b/twilio/task_router/capability/capability_api.py @@ -0,0 +1,68 @@ +import time + +from .. import jwt + + +class CapabilityAPI(object): + """ + A token to control permissions for the TaskRouter service. + + :param str account_sid: The account to generate a token for + :param str auth_token: The auth token for the account. Used to sign the + token and will not be included in generated output. + :param str workspace_sid: The workspace to grant capabilities over + :param str worker_sid: The worker sid to grant capabilities over + :param str base_url: The base TaskRouter API URL + :param str base_ws_url: The base TaskRouter event stream URL + """ + def __init__(self, account_sid, auth_token, version, friendly_name): + self.account_sid = account_sid + self.auth_token = auth_token + + self.version = version + self.friendly_name = friendly_name; + self.policies = [] + + def add_policy(self, url, method, allowed, query_filter = None, post_filter = None): + policy = self.make_policy(url, method, allowed, query_filter, post_filter) + self.policies.append(policy) + + def allow(self, url, method, query_filter = None, post_filter = None): + self.add_policy(url, method, True, query_filter, post_filter) + + def deny(self, url, method, query_filter = None, post_filter = None): + self.add_policy(url, method, False, query_filter, post_filter) + + def generate_token(self, ttl = 3600, attributes = None): + return self._generate_token(ttl) + + def _generate_token(self, ttl, attributes=None): + payload = { + 'iss': self.account_sid, + 'exp': int(time.time()) + ttl, + 'version': self.version, + 'friendly_name': self.friendly_name, + 'policies': self.policies, + } + + if attributes is not None: + payload.update(attributes) + + return jwt.encode(payload, self.auth_token, 'HS256') + + def make_policy(self, url, method, allowed = True, query_filter = None, post_filter = None): + # Create a policy dictionary for the given resource and method. + + # :param str url: the resource URL to grant or deny access to + # :param str method: the HTTP method to allow or deny + # :param allowed bool: whether this request is allowed + # :param dict query_filter: specific GET parameter names to require or allow + # :param dict post_filter: POST parameter names to require or allow + + return { + 'url': url, + 'method': method, + 'allowed': allowed, + 'query_filter': query_filter or {}, + 'post_filter': post_filter or {} + } \ No newline at end of file diff --git a/twilio/task_router/capability/task_router_capability.py b/twilio/task_router/capability/task_router_capability.py new file mode 100644 index 0000000000..c6f69b392f --- /dev/null +++ b/twilio/task_router/capability/task_router_capability.py @@ -0,0 +1,184 @@ +from .capability_api import CapabilityAPI + +import warnings +warnings.simplefilter('always', DeprecationWarning) + +TASK_ROUTER_BASE_URL = 'https://taskrouter.twilio.com' +TASK_ROUTER_BASE_EVENTS_URL = 'https://event-bridge.twilio.com/v1/wschannels' +TASK_ROUTER_VERSION = "v1" + +REQUIRED = {'required': True} +OPTIONAL = {'required': False} + +def deprecated(func): + def log_warning(*args, **kwargs): + # stacklevel = 2 makes the warning refer to the caller of the deprecation rather than the source of deprecation itself + warnings.warn("Call to deprecated function {}.".format(func.__name__), stacklevel = 2, category = DeprecationWarning) + return func(*args, **kwargs) + return log_warning + + +class TaskRouterCapability(CapabilityAPI): + def __init__(self, account_sid, auth_token, workspace_sid, channel_id): + super(TaskRouterCapability, self).__init__(account_sid, auth_token, TASK_ROUTER_VERSION, channel_id) + + self.workspace_sid = workspace_sid + self.channel_id = channel_id + self.base_url = TASK_ROUTER_BASE_URL + "/" + TASK_ROUTER_VERSION + "/Workspaces/" + workspace_sid + + # validate the JWT + self.validate_jwt() + + # set up resources + self.setup_resource() + + # add permissions to GET and POST to the event-bridge channel + self.allow_web_sockets(channel_id) + + # add permissions to fetch the instance resource + self.add_policy(self.resource_url, "GET", True) + + def setup_resource(self): + if self.channel_id[0:2] == "WS": + self.resource_url = self.base_url + elif self.channel_id[0:2] == "WK": + self.resource_url = self.base_url + "/Workers/" + self.channel_id + activity_url = self.base_url + "/Activities" + self.allow(activity_url, "GET") + elif self.channel_id[0:2] == "WQ": + self.resource_url = self.base_url + "/TaskQueues/" + self.channel_id + + def allow_web_sockets(self, channel_id): + web_socket_url = TASK_ROUTER_BASE_EVENTS_URL + "/" + self.account_sid + "/" + self.channel_id; + self.policies.append(self.make_policy(web_socket_url, "GET", True)) + self.policies.append(self.make_policy(web_socket_url, "POST", True)) + + def validate_jwt(self): + if self.account_sid is None or self.account_sid[0:2] != "AC": + raise ValueError('Invalid AccountSid provided: ' + self.account_sid) + if self.workspace_sid is None or self.workspace_sid[0:2] != "WS": + raise ValueError('Invalid WorkspaceSid provided: ' + self.workspace_sid) + if self.channel_id is None: + raise ValueError('ChannelId not provided') + + prefix = self.channel_id[0:2] + if prefix != "WS" and prefix != "WK" and prefix != "WQ": + raise ValueError('Invalid ChannelId provided: ' + self.channel_id) + + def allow_fetch_subresources(self): + self.allow(self.resource_url + "/**", "GET") + + def allow_updates(self): + self.allow(self.resource_url, "POST") + + def allow_updates_subresources(self): + self.allow(self.resource_url + "/**", "POST") + + def allow_delete(self): + self.allow(self.resource_url, "DELETE") + + def allow_delete_subresources(self): + self.allow(self.resource_url + "/**", "DELETE") + + @deprecated + def allow_worker_fetch_attributes(self): + if self.channel_id[0:2] == "WK": + self.policies.append(self.make_policy( + self.resource_url, + 'GET' + ) + ) + else: + raise ValueError("Deprecated function not applicable to non Worker") + + @deprecated + def allow_worker_activity_updates(self): + if self.channel_id[0:2] == "WK": + self.policies.append(self.make_policy( + self.resource_url, + 'POST', + True, + post_filter = {'ActivitySid': REQUIRED} + ) + ) + else: + raise ValueError("Deprecated function not applicable to non Worker") + + + @deprecated + def allow_task_reservation_updates(self): + if self.channel_id[0:2] == "WK": + tasks_url = self.base_url + "/Tasks/**" + self.policies.append(self.make_policy( + tasks_url, + 'POST', + True, + post_filter = {'ReservationStatus': REQUIRED}, + ) + ) + else: + raise ValueError("Deprecated function not applicable to non Worker") + + def get_resource_url(self): + return self.resource_url + + def generate_token(self, ttl = 3600): + task_router_attributes = {} + task_router_attributes["account_sid"] = self.account_sid + task_router_attributes["workspace_sid"] = self.workspace_sid + task_router_attributes["channel"] = self.channel_id + + if self.channel_id[0:2] == "WK": + task_router_attributes["worker_sid"] = self.channel_id + elif self.channel_id[0:2] == "WQ": + task_router_attributes["taskqueue_sid"] = self.channel_id + + return self._generate_token(ttl, task_router_attributes) + +class TaskRouterWorkerCapability(TaskRouterCapability): + def __init__(self, account_sid, auth_token, workspace_sid, worker_sid): + super(TaskRouterWorkerCapability, self).__init__(account_sid, auth_token, workspace_sid, worker_sid) + + self.reservations_url = self.base_url + "/Tasks/**" + self.activity_url = self.base_url + "/Activities" + + # add permissions to fetch the list of activities and list of worker reservations + self.allow(self.reservations_url, "GET") + self.allow(self.activity_url, "GET") + + def setup_resource(self): + self.resource_url = self.base_url + "/Workers/" + self.channel_id + + def allow_activity_updates(self): + self.policies.append(self.make_policy( + self.resource_url, + 'POST', + True, + post_filter = {'ActivitySid': REQUIRED} + ) + ) + def allow_reservation_updates(self): + self.policies.append(self.make_policy( + self.reservations_url, + 'POST', + True, + post_filter = {'ReservationStatus': REQUIRED}, + ) + ) + +class TaskRouterTaskQueueCapability(TaskRouterCapability): + def __init__(self, account_sid, auth_token, workspace_sid, taskqueue_sid): + super(TaskRouterTaskQueueCapability, self).__init__(account_sid, auth_token, workspace_sid, taskqueue_sid) + + def setup_resource(self): + self.resource_url = self.base_url + "/TaskQueues/" + self.channel_id + + +class TaskRouterWorkspaceCapability(TaskRouterCapability): + def __init__(self, account_sid, auth_token, workspace_sid): + super(TaskRouterWorkspaceCapability, self).__init__(account_sid, auth_token, workspace_sid, workspace_sid) + + def setup_resource(self): + self.resource_url = self.base_url + + From 73f40e5778ef44894a6da3c66b6d52277906f52e Mon Sep 17 00:00:00 2001 From: Jen Li Date: Fri, 26 Jun 2015 15:16:45 -0700 Subject: [PATCH 02/42] changed __init__.py file --- twilio/task_router/__init__.py | 10 ++++++++++ twilio/task_router/capability/__init__.py | 10 ---------- 2 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 twilio/task_router/__init__.py delete mode 100644 twilio/task_router/capability/__init__.py diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py new file mode 100644 index 0000000000..cf486bcc35 --- /dev/null +++ b/twilio/task_router/__init__.py @@ -0,0 +1,10 @@ +from .. import jwt +import time + +from .capability import ( + CapabilityAPI, + TaskRouterCapability, + TaskRouterWorkerCapability, + TaskRouterTaskQueueCapability, + TaskRouterWorkspaceCapability +) \ No newline at end of file diff --git a/twilio/task_router/capability/__init__.py b/twilio/task_router/capability/__init__.py deleted file mode 100644 index 73e66978ef..0000000000 --- a/twilio/task_router/capability/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from .capability_api import ( - CapabilityAPI -) - -from .task_router_capability import ( - TaskRouterCapability, - TaskRouterWorkerCapability, - TaskRouterTaskQueueCapability, - TaskRouterWorkspaceCapability -) From 55c41884ac46a54a62401353824f554f0afab688 Mon Sep 17 00:00:00 2001 From: Jen Li Date: Fri, 26 Jun 2015 15:59:51 -0700 Subject: [PATCH 03/42] no reservation status --- tests/task_router/test_task_router_worker_capability.py | 4 ++-- twilio/task_router/capability/task_router_capability.py | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py index 030190747d..c36014b303 100644 --- a/tests/task_router/test_task_router_worker_capability.py +++ b/tests/task_router/test_task_router_worker_capability.py @@ -132,7 +132,7 @@ def test_allow_activity_updates(self): self.assertTrue(policy["allowed"]) self.assertIsNotNone(policy['post_filter']) self.assertEqual({}, policy['query_filter']) - self.assertTrue(policy['post_filter']['ActivitySid']) + self.assertEqual({}, policy['post_filter']) def test_allow_reservation_updates(self): # allow reservation updates @@ -156,7 +156,7 @@ def test_allow_reservation_updates(self): self.assertTrue(policy["allowed"]) self.assertIsNotNone(policy["post_filter"]) self.assertEqual({}, policy["query_filter"]) - self.assertTrue(policy["post_filter"]["ReservationStatus"]) + self.assertEqual({}, policy['post_filter']) if __name__ == "__main__": unittest.main() diff --git a/twilio/task_router/capability/task_router_capability.py b/twilio/task_router/capability/task_router_capability.py index c6f69b392f..f4ecf9d03e 100644 --- a/twilio/task_router/capability/task_router_capability.py +++ b/twilio/task_router/capability/task_router_capability.py @@ -153,16 +153,14 @@ def allow_activity_updates(self): self.policies.append(self.make_policy( self.resource_url, 'POST', - True, - post_filter = {'ActivitySid': REQUIRED} + True ) ) def allow_reservation_updates(self): self.policies.append(self.make_policy( self.reservations_url, 'POST', - True, - post_filter = {'ReservationStatus': REQUIRED}, + True ) ) From b37a9c1bae635bacbac9c8fcce69a5d02a25f77a Mon Sep 17 00:00:00 2001 From: Jen Li Date: Fri, 26 Jun 2015 16:01:54 -0700 Subject: [PATCH 04/42] wrong reservation status changed --- twilio/task_router/capability/task_router_capability.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/twilio/task_router/capability/task_router_capability.py b/twilio/task_router/capability/task_router_capability.py index f4ecf9d03e..e5a6d5e3e0 100644 --- a/twilio/task_router/capability/task_router_capability.py +++ b/twilio/task_router/capability/task_router_capability.py @@ -113,7 +113,6 @@ def allow_task_reservation_updates(self): tasks_url, 'POST', True, - post_filter = {'ReservationStatus': REQUIRED}, ) ) else: @@ -153,7 +152,8 @@ def allow_activity_updates(self): self.policies.append(self.make_policy( self.resource_url, 'POST', - True + True, + post_filter = {'ActivitySid': REQUIRED} ) ) def allow_reservation_updates(self): From 7f987b13951a2bd9466eaf428792dc8c4fde5680 Mon Sep 17 00:00:00 2001 From: Jen Li Date: Mon, 29 Jun 2015 16:59:24 -0700 Subject: [PATCH 05/42] bug fix to fetch reservations --- .../test_task_router_capability.py | 42 +++++++++++++------ .../test_task_router_worker_capability.py | 3 +- .../capability/task_router_capability.py | 5 +++ 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py index 35b1cb7678..d700fd44c6 100644 --- a/tests/task_router/test_task_router_capability.py +++ b/tests/task_router/test_task_router_capability.py @@ -9,7 +9,7 @@ class TaskRouterCapabilityTest(unittest.TestCase): - def test_workspace_default(self): + def test_workspace_default(self): account_sid = "AC123" auth_token = "foobar" workspace_sid = "WS456" @@ -58,7 +58,7 @@ def test_workspace_default(self): self.assertEqual({}, fetch_policy['query_filter']) self.assertEqual({}, fetch_policy['post_filter']) - def test_worker_default(self): + def test_worker_default(self): account_sid = "AC123" auth_token = "foobar" workspace_sid = "WS456" @@ -82,7 +82,7 @@ def test_worker_default(self): self.assertEqual(decoded["friendly_name"], worker_sid) policies = decoded['policies'] - self.assertEqual(len(policies), 4) + self.assertEqual(len(policies), 5) # activity GET fetch_activity = policies[0] @@ -92,8 +92,16 @@ def test_worker_default(self): self.assertEqual({}, fetch_activity['query_filter']) self.assertEqual({}, fetch_activity['post_filter']) + # reservation GET + fetch_reservation = policies[1] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", fetch_reservation['url']) + self.assertEqual("GET", fetch_reservation['method']) + self.assertTrue(fetch_reservation['allowed']) + self.assertEqual({}, fetch_reservation['query_filter']) + self.assertEqual({}, fetch_reservation['post_filter']) + # websocket GET - get_policy = policies[1] + get_policy = policies[2] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", get_policy['url']) self.assertEqual("GET", get_policy['method']) self.assertTrue(get_policy['allowed']) @@ -101,7 +109,7 @@ def test_worker_default(self): self.assertEqual({}, get_policy['post_filter']) # websocket POST - post_policy = policies[2] + post_policy = policies[3] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", post_policy['url']) self.assertEqual("POST", post_policy['method']) self.assertTrue(post_policy['allowed']) @@ -109,14 +117,14 @@ def test_worker_default(self): self.assertEqual({}, post_policy['post_filter']) # fetch GET - fetch_policy = policies[3] + fetch_policy = policies[4] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", fetch_policy['url']) self.assertEqual("GET", fetch_policy['method']) self.assertTrue(fetch_policy['allowed']) self.assertEqual({}, fetch_policy['query_filter']) self.assertEqual({}, fetch_policy['post_filter']) - def test_task_queue_default(self): + def test_task_queue_default(self): account_sid = "AC123" auth_token = "foobar" workspace_sid = "WS456" @@ -190,9 +198,9 @@ def test_deprecated_worker(self): self.assertEqual(decoded["friendly_name"], worker_sid) policies = decoded['policies'] - self.assertEqual(len(policies), 4) + self.assertEqual(len(policies), 5) - # should expect 4 policies + # should expect 5 policies # activity GET fetch_activity = policies[0] @@ -202,8 +210,16 @@ def test_deprecated_worker(self): self.assertEqual({}, fetch_activity['query_filter']) self.assertEqual({}, fetch_activity['post_filter']) + # reservation GET + fetch_reservation = policies[1] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", fetch_reservation['url']) + self.assertEqual("GET", fetch_reservation['method']) + self.assertTrue(fetch_reservation['allowed']) + self.assertEqual({}, fetch_reservation['query_filter']) + self.assertEqual({}, fetch_reservation['post_filter']) + # websocket GET - get_policy = policies[1] + get_policy = policies[2] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", get_policy['url']) self.assertEqual("GET", get_policy['method']) self.assertTrue(get_policy['allowed']) @@ -211,7 +227,7 @@ def test_deprecated_worker(self): self.assertEqual({}, get_policy['post_filter']) # websocket POST - post_policy = policies[2] + post_policy = policies[3] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", post_policy['url']) self.assertEqual("POST", post_policy['method']) self.assertTrue(post_policy['allowed']) @@ -219,7 +235,7 @@ def test_deprecated_worker(self): self.assertEqual({}, post_policy['post_filter']) # fetch GET - fetch_policy = policies[3] + fetch_policy = policies[4] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", fetch_policy['url']) self.assertEqual("GET", fetch_policy['method']) self.assertTrue(fetch_policy['allowed']) @@ -250,4 +266,4 @@ def test_deprecated_worker(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py index c36014b303..ecd9509cb1 100644 --- a/tests/task_router/test_task_router_worker_capability.py +++ b/tests/task_router/test_task_router_worker_capability.py @@ -1,5 +1,4 @@ import sys - import time import unittest import warnings @@ -132,7 +131,7 @@ def test_allow_activity_updates(self): self.assertTrue(policy["allowed"]) self.assertIsNotNone(policy['post_filter']) self.assertEqual({}, policy['query_filter']) - self.assertEqual({}, policy['post_filter']) + self.assertTrue(policy['post_filter']['ActivitySid']) def test_allow_reservation_updates(self): # allow reservation updates diff --git a/twilio/task_router/capability/task_router_capability.py b/twilio/task_router/capability/task_router_capability.py index e5a6d5e3e0..6758020af6 100644 --- a/twilio/task_router/capability/task_router_capability.py +++ b/twilio/task_router/capability/task_router_capability.py @@ -43,8 +43,13 @@ def setup_resource(self): self.resource_url = self.base_url elif self.channel_id[0:2] == "WK": self.resource_url = self.base_url + "/Workers/" + self.channel_id + activity_url = self.base_url + "/Activities" self.allow(activity_url, "GET") + + reservations_url = self.base_url + "/Tasks/**" + self.allow(reservations_url, "GET") + elif self.channel_id[0:2] == "WQ": self.resource_url = self.base_url + "/TaskQueues/" + self.channel_id From 7be91635b64092b22c0b51d474429200511cfe10 Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Wed, 15 Jul 2015 09:44:47 -0700 Subject: [PATCH 06/42] Remove CapabilityAPI Fix formatting problems (whitespaces at end of lines, line length of 80 chars, two spaces before class names, new lines at end of file, no spaces between default parameter attributes,) Fix old capability test --- tests/task_router/test_capability.py | 24 +- .../test_task_router_capability.py | 39 ++- .../test_task_router_taskqueue_capability.py | 184 +++++------ .../test_task_router_worker_capability.py | 302 +++++++++--------- .../test_task_router_workspace_capability.py | 181 ++++++----- twilio/task_router/__init__.py | 255 ++++++++++++++- .../task_router/capability/capability_api.py | 68 ---- .../capability/task_router_capability.py | 187 ----------- 8 files changed, 617 insertions(+), 623 deletions(-) delete mode 100644 twilio/task_router/capability/capability_api.py delete mode 100644 twilio/task_router/capability/task_router_capability.py diff --git a/tests/task_router/test_capability.py b/tests/task_router/test_capability.py index abfb25b0f5..707fe97f83 100644 --- a/tests/task_router/test_capability.py +++ b/tests/task_router/test_capability.py @@ -59,6 +59,22 @@ def test_defaults(self): (self.account_sid, self.worker_sid) ) expected = [ + { + 'url': + 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities', + 'method': 'GET', + 'allow': True, + 'query_filter': {}, + 'post_filter': {}, + }, + { + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/%s/Tasks/**' % + (self.workspace_sid), + 'method': 'GET', + 'allow': True, + 'query_filter': {}, + 'post_filter': {}, + }, { 'url': websocket_url, 'method': 'GET', @@ -74,13 +90,13 @@ def test_defaults(self): 'post_filter': {}, }, { - 'url': - 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities', + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s' % + (self.workspace_sid, self.worker_sid), 'method': 'GET', 'allow': True, 'query_filter': {}, 'post_filter': {}, - }, + } ] self.assertEqual(expected, decoded['policies']) @@ -140,6 +156,6 @@ def test_allow_task_reservation_updates(self): 'method': 'POST', 'allow': True, 'query_filter': {}, - 'post_filter': {'ReservationStatus': {'required': True}}, + 'post_filter': {}, } self.assertEqual(expected, decoded['policies'][-1]) diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py index d700fd44c6..9cbe90c1aa 100644 --- a/tests/task_router/test_task_router_capability.py +++ b/tests/task_router/test_task_router_capability.py @@ -1,11 +1,9 @@ -import sys - -import time import unittest import warnings from twilio import jwt -from twilio.task_router.capability import TaskRouterCapability +from twilio.task_router import TaskRouterCapability + class TaskRouterCapabilityTest(unittest.TestCase): @@ -38,7 +36,7 @@ def test_workspace_default(self): get_policy = policies[0] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", get_policy['url']) self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allowed']) + self.assertTrue(get_policy['allow']) self.assertEqual({}, get_policy['query_filter']) self.assertEqual({}, get_policy['post_filter']) @@ -46,7 +44,7 @@ def test_workspace_default(self): post_policy = policies[1] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", post_policy['url']) self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allowed']) + self.assertTrue(post_policy['allow']) self.assertEqual({}, post_policy['query_filter']) self.assertEqual({}, post_policy['post_filter']) @@ -54,7 +52,7 @@ def test_workspace_default(self): fetch_policy = policies[2] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456", fetch_policy['url']) self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allowed']) + self.assertTrue(fetch_policy['allow']) self.assertEqual({}, fetch_policy['query_filter']) self.assertEqual({}, fetch_policy['post_filter']) @@ -88,7 +86,7 @@ def test_worker_default(self): fetch_activity = policies[0] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", fetch_activity['url']) self.assertEqual("GET", fetch_activity['method']) - self.assertTrue(fetch_activity['allowed']) + self.assertTrue(fetch_activity['allow']) self.assertEqual({}, fetch_activity['query_filter']) self.assertEqual({}, fetch_activity['post_filter']) @@ -96,7 +94,7 @@ def test_worker_default(self): fetch_reservation = policies[1] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", fetch_reservation['url']) self.assertEqual("GET", fetch_reservation['method']) - self.assertTrue(fetch_reservation['allowed']) + self.assertTrue(fetch_reservation['allow']) self.assertEqual({}, fetch_reservation['query_filter']) self.assertEqual({}, fetch_reservation['post_filter']) @@ -104,7 +102,7 @@ def test_worker_default(self): get_policy = policies[2] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", get_policy['url']) self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allowed']) + self.assertTrue(get_policy['allow']) self.assertEqual({}, get_policy['query_filter']) self.assertEqual({}, get_policy['post_filter']) @@ -112,7 +110,7 @@ def test_worker_default(self): post_policy = policies[3] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", post_policy['url']) self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allowed']) + self.assertTrue(post_policy['allow']) self.assertEqual({}, post_policy['query_filter']) self.assertEqual({}, post_policy['post_filter']) @@ -120,7 +118,7 @@ def test_worker_default(self): fetch_policy = policies[4] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", fetch_policy['url']) self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allowed']) + self.assertTrue(fetch_policy['allow']) self.assertEqual({}, fetch_policy['query_filter']) self.assertEqual({}, fetch_policy['post_filter']) @@ -154,7 +152,7 @@ def test_task_queue_default(self): get_policy = policies[0] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", get_policy['url']) self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allowed']) + self.assertTrue(get_policy['allow']) self.assertEqual({}, get_policy['query_filter']) self.assertEqual({}, get_policy['post_filter']) @@ -162,7 +160,7 @@ def test_task_queue_default(self): post_policy = policies[1] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", post_policy['url']) self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allowed']) + self.assertTrue(post_policy['allow']) self.assertEqual({}, post_policy['query_filter']) self.assertEqual({}, post_policy['post_filter']) @@ -170,7 +168,7 @@ def test_task_queue_default(self): fetch_policy = policies[2] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789", fetch_policy['url']) self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allowed']) + self.assertTrue(fetch_policy['allow']) self.assertEqual({}, fetch_policy['query_filter']) self.assertEqual({}, fetch_policy['post_filter']) @@ -206,7 +204,7 @@ def test_deprecated_worker(self): fetch_activity = policies[0] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", fetch_activity['url']) self.assertEqual("GET", fetch_activity['method']) - self.assertTrue(fetch_activity['allowed']) + self.assertTrue(fetch_activity['allow']) self.assertEqual({}, fetch_activity['query_filter']) self.assertEqual({}, fetch_activity['post_filter']) @@ -214,7 +212,7 @@ def test_deprecated_worker(self): fetch_reservation = policies[1] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", fetch_reservation['url']) self.assertEqual("GET", fetch_reservation['method']) - self.assertTrue(fetch_reservation['allowed']) + self.assertTrue(fetch_reservation['allow']) self.assertEqual({}, fetch_reservation['query_filter']) self.assertEqual({}, fetch_reservation['post_filter']) @@ -222,7 +220,7 @@ def test_deprecated_worker(self): get_policy = policies[2] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", get_policy['url']) self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allowed']) + self.assertTrue(get_policy['allow']) self.assertEqual({}, get_policy['query_filter']) self.assertEqual({}, get_policy['post_filter']) @@ -230,7 +228,7 @@ def test_deprecated_worker(self): post_policy = policies[3] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", post_policy['url']) self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allowed']) + self.assertTrue(post_policy['allow']) self.assertEqual({}, post_policy['query_filter']) self.assertEqual({}, post_policy['post_filter']) @@ -238,7 +236,7 @@ def test_deprecated_worker(self): fetch_policy = policies[4] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", fetch_policy['url']) self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allowed']) + self.assertTrue(fetch_policy['allow']) self.assertEqual({}, fetch_policy['query_filter']) self.assertEqual({}, fetch_policy['post_filter']) @@ -264,6 +262,5 @@ def test_deprecated_worker(self): assert issubclass(w[-1].category, DeprecationWarning) assert "deprecated" in str(w[-1].message) - if __name__ == "__main__": unittest.main() diff --git a/tests/task_router/test_task_router_taskqueue_capability.py b/tests/task_router/test_task_router_taskqueue_capability.py index bd3cec6295..66f466a763 100644 --- a/tests/task_router/test_task_router_taskqueue_capability.py +++ b/tests/task_router/test_task_router_taskqueue_capability.py @@ -1,132 +1,132 @@ -import sys import time import unittest from twilio import jwt -from twilio.task_router.capability import TaskRouterTaskQueueCapability +from twilio.task_router import TaskRouterTaskQueueCapability + class TaskRouterTaskQueueCapabilityTest(unittest.TestCase): - def setUp(self): - self.account_sid = "AC123" - self.auth_token = "foobar" - self.workspace_sid = "WS456" - self.taskqueue_sid = "WQ789" - self.capability = TaskRouterTaskQueueCapability(self.account_sid, self.auth_token, self.workspace_sid, self.taskqueue_sid) + def setUp(self): + self.account_sid = "AC123" + self.auth_token = "foobar" + self.workspace_sid = "WS456" + self.taskqueue_sid = "WQ789" + self.capability = TaskRouterTaskQueueCapability(self.account_sid, self.auth_token, self.workspace_sid, self.taskqueue_sid) - def test_generate_token(self): + def test_generate_token(self): - token = self.capability.generate_token() - self.assertIsNotNone(token) + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - self.assertEqual(decoded["iss"], self.account_sid) - self.assertEqual(decoded["account_sid"], self.account_sid) - self.assertEqual(decoded["workspace_sid"], self.workspace_sid) - self.assertEqual(decoded["taskqueue_sid"], self.taskqueue_sid) - self.assertEqual(decoded["channel"], self.taskqueue_sid) - self.assertEqual(decoded["version"], "v1") - self.assertEqual(decoded["friendly_name"], self.taskqueue_sid) + self.assertEqual(decoded["iss"], self.account_sid) + self.assertEqual(decoded["account_sid"], self.account_sid) + self.assertEqual(decoded["workspace_sid"], self.workspace_sid) + self.assertEqual(decoded["taskqueue_sid"], self.taskqueue_sid) + self.assertEqual(decoded["channel"], self.taskqueue_sid) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], self.taskqueue_sid) - def test_generate_token_with_default_ttl(self): - token = self.capability.generate_token() - self.assertIsNotNone(token) + def test_generate_token_with_default_ttl(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - self.assertEqual(int(time.time()) + 3600, decoded["exp"]) + self.assertEqual(int(time.time()) + 3600, decoded["exp"]) - def test_generate_token_with_custom_ttl(self): - ttl = 10000 + def test_generate_token_with_custom_ttl(self): + ttl = 10000 - token = self.capability.generate_token(ttl) - self.assertIsNotNone(token) + token = self.capability.generate_token(ttl) + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - self.assertEqual(int(time.time()) + 10000, decoded["exp"]) + self.assertEqual(int(time.time()) + 10000, decoded["exp"]) - def test_default(self): - token = self.capability.generate_token() - self.assertIsNotNone(token) + def test_default(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - policies = decoded['policies'] - self.assertEqual(len(policies), 3) + policies = decoded['policies'] + self.assertEqual(len(policies), 3) - # websocket GET - get_policy = policies[0] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", get_policy['url']) - self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allowed']) - self.assertEqual({}, get_policy['query_filter']) - self.assertEqual({}, get_policy['post_filter']) + # websocket GET + get_policy = policies[0] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", get_policy['url']) + self.assertEqual("GET", get_policy['method']) + self.assertTrue(get_policy['allow']) + self.assertEqual({}, get_policy['query_filter']) + self.assertEqual({}, get_policy['post_filter']) - # websocket POST - post_policy = policies[1] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", post_policy['url']) - self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allowed']) - self.assertEqual({}, post_policy['query_filter']) - self.assertEqual({}, post_policy['post_filter']) + # websocket POST + post_policy = policies[1] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", post_policy['url']) + self.assertEqual("POST", post_policy['method']) + self.assertTrue(post_policy['allow']) + self.assertEqual({}, post_policy['query_filter']) + self.assertEqual({}, post_policy['post_filter']) - # fetch GET - fetch_policy = policies[2] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789", fetch_policy['url']) - self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allowed']) - self.assertEqual({}, fetch_policy['query_filter']) - self.assertEqual({}, fetch_policy['post_filter']) + # fetch GET + fetch_policy = policies[2] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789", fetch_policy['url']) + self.assertEqual("GET", fetch_policy['method']) + self.assertTrue(fetch_policy['allow']) + self.assertEqual({}, fetch_policy['query_filter']) + self.assertEqual({}, fetch_policy['post_filter']) - def test_allow_fetch_subresources(self): - self.capability.allow_fetch_subresources() + def test_allow_fetch_subresources(self): + self.capability.allow_fetch_subresources() - token = self.capability.generate_token() - self.assertIsNotNone(token) + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - policies = decoded['policies'] - self.assertEqual(len(policies), 4) + policies = decoded['policies'] + self.assertEqual(len(policies), 4) - # confirm the additional policy generated with allow_fetch_subresources() + # confirm the additional policy generated with allow_fetch_subresources() - policy = policies[3] + policy = policies[3] - self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789/**") - self.assertEqual(policy['method'], "GET") - self.assertTrue(policy['allowed']) - self.assertEqual({}, policy['query_filter']) - self.assertEqual({}, policy['post_filter']) + self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789/**") + self.assertEqual(policy['method'], "GET") + self.assertTrue(policy['allow']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) - def test_allow_updates_subresources(self): - self.capability.allow_updates_subresources() + def test_allow_updates_subresources(self): + self.capability.allow_updates_subresources() - token = self.capability.generate_token() - self.assertIsNotNone(token) + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - policies = decoded['policies'] - self.assertEqual(len(policies), 4) + policies = decoded['policies'] + self.assertEqual(len(policies), 4) - # confirm the additional policy generated with allow_updates_subresources() + # confirm the additional policy generated with allow_updates_subresources() - policy = policies[3] + policy = policies[3] - self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789/**") - self.assertEqual(policy['method'], "POST") - self.assertTrue(policy['allowed']) - self.assertEqual({}, policy['query_filter']) - self.assertEqual({}, policy['post_filter']) + self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789/**") + self.assertEqual(policy['method'], "POST") + self.assertTrue(policy['allow']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) if __name__ == "__main__": - unittest.main() + unittest.main() diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py index ecd9509cb1..35abb5b831 100644 --- a/tests/task_router/test_task_router_worker_capability.py +++ b/tests/task_router/test_task_router_worker_capability.py @@ -1,161 +1,159 @@ -import sys import time import unittest -import warnings from twilio import jwt -from twilio.task_router.capability import TaskRouterWorkerCapability +from twilio.task_router import TaskRouterWorkerCapability + class TaskRouterWorkerCapabilityTest(unittest.TestCase): - def setUp(self): - self.account_sid = "AC123" - self.auth_token = "foobar" - self.workspace_sid = "WS456" - self.worker_sid = "WK789" - self.capability = TaskRouterWorkerCapability(self.account_sid, self.auth_token, self.workspace_sid, self.worker_sid) - - def test_generate_token(self): - - token = self.capability.generate_token() - self.assertIsNotNone(token) - - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) - - self.assertEqual(decoded["iss"], self.account_sid) - self.assertEqual(decoded["account_sid"], self.account_sid) - self.assertEqual(decoded["workspace_sid"], self.workspace_sid) - self.assertEqual(decoded["worker_sid"], self.worker_sid) - self.assertEqual(decoded["channel"], self.worker_sid) - self.assertEqual(decoded["version"], "v1") - self.assertEqual(decoded["friendly_name"], self.worker_sid) - - def test_generate_token_with_default_ttl(self): - token = self.capability.generate_token() - self.assertIsNotNone(token) - - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) - - self.assertEqual(int(time.time()) + 3600, decoded["exp"]) - - def test_generate_token_with_custom_ttl(self): - ttl = 10000 - - token = self.capability.generate_token(ttl) - self.assertIsNotNone(token) - - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) - - self.assertEqual(int(time.time()) + 10000, decoded["exp"]) - - def test_defaults(self): - token = self.capability.generate_token() - self.assertIsNotNone(token) - - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) - - websocket_url = 'https://event-bridge.twilio.com/v1/wschannels/%s/%s' % (self.account_sid, self.worker_sid) - - # expect 5 policies - policies = decoded['policies'] - self.assertEqual(len(policies), 5) - - # policy 0 - GET websocket - get_policy = policies[0] - self.assertIsNotNone(get_policy) - self.assertEqual(get_policy['url'], websocket_url) - self.assertEqual(get_policy['method'], 'GET') - self.assertTrue(get_policy['allowed']) - self.assertEqual(get_policy['query_filter'], {}) - self.assertEqual(get_policy['post_filter'], {}) - - # policy 1 - POST - post_policy = policies[1] - self.assertIsNotNone(post_policy) - self.assertEqual(post_policy['url'], websocket_url) - self.assertEqual(post_policy['method'], 'POST') - self.assertTrue(post_policy['allowed']) - self.assertEqual(post_policy['query_filter'], {}) - self.assertEqual(post_policy['post_filter'], {}) - - # policy 2 - Worker fetch - worker_fetch_policy = policies[2] - self.assertIsNotNone(worker_fetch_policy) - self.assertEqual(worker_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789') - self.assertEqual(worker_fetch_policy['method'], 'GET') - self.assertTrue(worker_fetch_policy['allowed']) - self.assertEqual(worker_fetch_policy['query_filter'], {}) - self.assertEqual(worker_fetch_policy['post_filter'], {}) - - # policy 3 - Reservation fetch - reservation_fetch_policy = policies[3] - self.assertIsNotNone(reservation_fetch_policy) - self.assertEqual(reservation_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**') - self.assertEqual(reservation_fetch_policy['method'], 'GET') - self.assertTrue(reservation_fetch_policy['allowed']) - self.assertEqual(reservation_fetch_policy['query_filter'], {}) - self.assertEqual(reservation_fetch_policy['post_filter'], {}) - - # policy 4 - Activity fetch - activity_fetch_policy = policies[4] - self.assertIsNotNone(activity_fetch_policy) - self.assertEqual(activity_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities') - self.assertEqual(activity_fetch_policy['method'], 'GET') - self.assertTrue(activity_fetch_policy['allowed']) - self.assertEqual(activity_fetch_policy['query_filter'], {}) - self.assertEqual(activity_fetch_policy['post_filter'], {}) - - def test_allow_activity_updates(self): - - # allow activity updates to the worker - self.capability.allow_activity_updates() - - token = self.capability.generate_token() - self.assertIsNotNone(token) - - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) - - policies = decoded['policies'] - self.assertEqual(len(policies), 6) - policy = policies[5] - - url = "https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s" % (self.workspace_sid, self.worker_sid) - - self.assertEqual(url, policy["url"]) - self.assertEqual("POST", policy["method"]) - self.assertTrue(policy["allowed"]) - self.assertIsNotNone(policy['post_filter']) - self.assertEqual({}, policy['query_filter']) - self.assertTrue(policy['post_filter']['ActivitySid']) - - def test_allow_reservation_updates(self): - # allow reservation updates - self.capability.allow_reservation_updates() - - token = self.capability.generate_token() - self.assertIsNotNone(token) - - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) - - policies = decoded['policies'] - self.assertEqual(len(policies), 6) - - policy = policies[5] - - url = "https://taskrouter.twilio.com/v1/Workspaces/%s/Tasks/**" % self.workspace_sid - - self.assertEqual(url, policy["url"]) - self.assertEqual("POST", policy["method"]) - self.assertTrue(policy["allowed"]) - self.assertIsNotNone(policy["post_filter"]) - self.assertEqual({}, policy["query_filter"]) - self.assertEqual({}, policy['post_filter']) + def setUp(self): + self.account_sid = "AC123" + self.auth_token = "foobar" + self.workspace_sid = "WS456" + self.worker_sid = "WK789" + self.capability = TaskRouterWorkerCapability(self.account_sid, self.auth_token, self.workspace_sid, self.worker_sid) + + def test_generate_token(self): + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(decoded["iss"], self.account_sid) + self.assertEqual(decoded["account_sid"], self.account_sid) + self.assertEqual(decoded["workspace_sid"], self.workspace_sid) + self.assertEqual(decoded["worker_sid"], self.worker_sid) + self.assertEqual(decoded["channel"], self.worker_sid) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], self.worker_sid) + + def test_generate_token_with_default_ttl(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(int(time.time()) + 3600, decoded["exp"]) + + def test_generate_token_with_custom_ttl(self): + ttl = 10000 + + token = self.capability.generate_token(ttl) + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(int(time.time()) + 10000, decoded["exp"]) + + def test_defaults(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + websocket_url = 'https://event-bridge.twilio.com/v1/wschannels/%s/%s' % (self.account_sid, self.worker_sid) + + # expect 5 policies + policies = decoded['policies'] + self.assertEqual(len(policies), 5) + + # policy 0 - GET websocket + get_policy = policies[0] + self.assertIsNotNone(get_policy) + self.assertEqual(get_policy['url'], websocket_url) + self.assertEqual(get_policy['method'], 'GET') + self.assertTrue(get_policy['allow']) + self.assertEqual(get_policy['query_filter'], {}) + self.assertEqual(get_policy['post_filter'], {}) + + # policy 1 - POST + post_policy = policies[1] + self.assertIsNotNone(post_policy) + self.assertEqual(post_policy['url'], websocket_url) + self.assertEqual(post_policy['method'], 'POST') + self.assertTrue(post_policy['allow']) + self.assertEqual(post_policy['query_filter'], {}) + self.assertEqual(post_policy['post_filter'], {}) + + # policy 2 - Worker fetch + worker_fetch_policy = policies[2] + self.assertIsNotNone(worker_fetch_policy) + self.assertEqual(worker_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789') + self.assertEqual(worker_fetch_policy['method'], 'GET') + self.assertTrue(worker_fetch_policy['allow']) + self.assertEqual(worker_fetch_policy['query_filter'], {}) + self.assertEqual(worker_fetch_policy['post_filter'], {}) + + # policy 3 - Reservation fetch + reservation_fetch_policy = policies[3] + self.assertIsNotNone(reservation_fetch_policy) + self.assertEqual(reservation_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**') + self.assertEqual(reservation_fetch_policy['method'], 'GET') + self.assertTrue(reservation_fetch_policy['allow']) + self.assertEqual(reservation_fetch_policy['query_filter'], {}) + self.assertEqual(reservation_fetch_policy['post_filter'], {}) + + # policy 4 - Activity fetch + activity_fetch_policy = policies[4] + self.assertIsNotNone(activity_fetch_policy) + self.assertEqual(activity_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities') + self.assertEqual(activity_fetch_policy['method'], 'GET') + self.assertTrue(activity_fetch_policy['allow']) + self.assertEqual(activity_fetch_policy['query_filter'], {}) + self.assertEqual(activity_fetch_policy['post_filter'], {}) + + def test_allow_activity_updates(self): + + # allow activity updates to the worker + self.capability.allow_activity_updates() + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 6) + policy = policies[5] + + url = "https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s" % (self.workspace_sid, self.worker_sid) + + self.assertEqual(url, policy["url"]) + self.assertEqual("POST", policy["method"]) + self.assertTrue(policy["allow"]) + self.assertIsNotNone(policy['post_filter']) + self.assertEqual({}, policy['query_filter']) + self.assertTrue(policy['post_filter']['ActivitySid']) + + def test_allow_reservation_updates(self): + # allow reservation updates + self.capability.allow_reservation_updates() + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 6) + + policy = policies[5] + + url = "https://taskrouter.twilio.com/v1/Workspaces/%s/Tasks/**" % self.workspace_sid + + self.assertEqual(url, policy["url"]) + self.assertEqual("POST", policy["method"]) + self.assertTrue(policy["allow"]) + self.assertEqual({}, policy["query_filter"]) + self.assertEqual({}, policy['post_filter']) if __name__ == "__main__": - unittest.main() + unittest.main() diff --git a/tests/task_router/test_task_router_workspace_capability.py b/tests/task_router/test_task_router_workspace_capability.py index cc389f7120..b35e692897 100644 --- a/tests/task_router/test_task_router_workspace_capability.py +++ b/tests/task_router/test_task_router_workspace_capability.py @@ -1,131 +1,130 @@ -import sys - import time import unittest from twilio import jwt -from twilio.task_router.capability import TaskRouterWorkspaceCapability +from twilio.task_router import TaskRouterWorkspaceCapability + class TaskRouterWorkspaceCapabilityTest(unittest.TestCase): - def setUp(self): - self.account_sid = "AC123" - self.auth_token = "foobar" - self.workspace_sid = "WS456" - self.capability = TaskRouterWorkspaceCapability(self.account_sid, self.auth_token, self.workspace_sid) + def setUp(self): + self.account_sid = "AC123" + self.auth_token = "foobar" + self.workspace_sid = "WS456" + self.capability = TaskRouterWorkspaceCapability(self.account_sid, self.auth_token, self.workspace_sid) - def test_generate_token(self): + def test_generate_token(self): - token = self.capability.generate_token() - self.assertIsNotNone(token) + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - self.assertEqual(decoded["iss"], self.account_sid) - self.assertEqual(decoded["account_sid"], self.account_sid) - self.assertEqual(decoded["workspace_sid"], self.workspace_sid) - self.assertEqual(decoded["channel"], self.workspace_sid) - self.assertEqual(decoded["version"], "v1") - self.assertEqual(decoded["friendly_name"], self.workspace_sid) + self.assertEqual(decoded["iss"], self.account_sid) + self.assertEqual(decoded["account_sid"], self.account_sid) + self.assertEqual(decoded["workspace_sid"], self.workspace_sid) + self.assertEqual(decoded["channel"], self.workspace_sid) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], self.workspace_sid) - def test_generate_token_with_default_ttl(self): - token = self.capability.generate_token() - self.assertIsNotNone(token) + def test_generate_token_with_default_ttl(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - self.assertEqual(int(time.time()) + 3600, decoded["exp"]) + self.assertEqual(int(time.time()) + 3600, decoded["exp"]) - def test_generate_token_with_custom_ttl(self): - ttl = 10000 + def test_generate_token_with_custom_ttl(self): + ttl = 10000 - token = self.capability.generate_token(ttl) - self.assertIsNotNone(token) + token = self.capability.generate_token(ttl) + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - self.assertEqual(int(time.time()) + 10000, decoded["exp"]) + self.assertEqual(int(time.time()) + 10000, decoded["exp"]) - def test_default(self): - token = self.capability.generate_token() - self.assertIsNotNone(token) + def test_default(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - policies = decoded['policies'] - self.assertEqual(len(policies), 3) + policies = decoded['policies'] + self.assertEqual(len(policies), 3) - # websocket GET - get_policy = policies[0] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", get_policy['url']) - self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allowed']) - self.assertEqual({}, get_policy['query_filter']) - self.assertEqual({}, get_policy['post_filter']) + # websocket GET + get_policy = policies[0] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", get_policy['url']) + self.assertEqual("GET", get_policy['method']) + self.assertTrue(get_policy['allow']) + self.assertEqual({}, get_policy['query_filter']) + self.assertEqual({}, get_policy['post_filter']) - # websocket POST - post_policy = policies[1] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", post_policy['url']) - self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allowed']) - self.assertEqual({}, post_policy['query_filter']) - self.assertEqual({}, post_policy['post_filter']) + # websocket POST + post_policy = policies[1] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", post_policy['url']) + self.assertEqual("POST", post_policy['method']) + self.assertTrue(post_policy['allow']) + self.assertEqual({}, post_policy['query_filter']) + self.assertEqual({}, post_policy['post_filter']) - # fetch GET - fetch_policy = policies[2] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456", fetch_policy['url']) - self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allowed']) - self.assertEqual({}, fetch_policy['query_filter']) - self.assertEqual({}, fetch_policy['post_filter']) + # fetch GET + fetch_policy = policies[2] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456", fetch_policy['url']) + self.assertEqual("GET", fetch_policy['method']) + self.assertTrue(fetch_policy['allow']) + self.assertEqual({}, fetch_policy['query_filter']) + self.assertEqual({}, fetch_policy['post_filter']) - def test_allow_fetch_subresources(self): - self.capability.allow_fetch_subresources() + def test_allow_fetch_subresources(self): + self.capability.allow_fetch_subresources() - token = self.capability.generate_token() - self.assertIsNotNone(token) + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - policies = decoded['policies'] - self.assertEqual(len(policies), 4) + policies = decoded['policies'] + self.assertEqual(len(policies), 4) - # confirm the additional policy generated with allow_fetch_subresources() + # confirm the additional policy generated with allow_fetch_subresources() - policy = policies[3] + policy = policies[3] - self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/**") - self.assertEqual(policy['method'], "GET") - self.assertTrue(policy['allowed']) - self.assertEqual({}, policy['query_filter']) - self.assertEqual({}, policy['post_filter']) + self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/**") + self.assertEqual(policy['method'], "GET") + self.assertTrue(policy['allow']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) - def test_allow_updates_subresources(self): - self.capability.allow_updates_subresources() + def test_allow_updates_subresources(self): + self.capability.allow_updates_subresources() - token = self.capability.generate_token() - self.assertIsNotNone(token) + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - policies = decoded['policies'] - self.assertEqual(len(policies), 4) + policies = decoded['policies'] + self.assertEqual(len(policies), 4) - # confirm the additional policy generated with allow_updates_subresources() + # confirm the additional policy generated with allow_updates_subresources() - policy = policies[3] + policy = policies[3] - self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/**") - self.assertEqual(policy['method'], "POST") - self.assertTrue(policy['allowed']) - self.assertEqual({}, policy['query_filter']) - self.assertEqual({}, policy['post_filter']) + self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/**") + self.assertEqual(policy['method'], "POST") + self.assertTrue(policy['allow']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index cf486bcc35..58a313d5e2 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -1,10 +1,249 @@ -from .. import jwt import time +from .. import jwt + +import warnings +warnings.simplefilter('always', DeprecationWarning) + +TASK_ROUTER_BASE_URL = 'https://taskrouter.twilio.com' +TASK_ROUTER_BASE_EVENTS_URL = 'https://event-bridge.twilio.com/v1/wschannels' +TASK_ROUTER_VERSION = "v1" + +REQUIRED = {'required': True} +OPTIONAL = {'required': False} + + +def deprecated(func): + def log_warning(*args, **kwargs): + # stacklevel = 2 makes the warning refer to the caller of the + # deprecation rather than the source of deprecation itself + warnings.warn("Call to deprecated function {}.". + format(func.__name__), + stacklevel=2, + category=DeprecationWarning) + return func(*args, **kwargs) + return log_warning + + +class TaskRouterCapability(object): + def __init__(self, account_sid, auth_token, workspace_sid, channel_id): + self.account_sid = account_sid + self.auth_token = auth_token + self.policies = [] + + self.workspace_sid = workspace_sid + self.channel_id = channel_id + self.base_url = (TASK_ROUTER_BASE_URL + "/" + + TASK_ROUTER_VERSION + + "/Workspaces/" + workspace_sid) + + # validate the JWT + self.validate_jwt() + + # set up resources + self.setup_resource() + + # add permissions to GET and POST to the event-bridge channel + self.allow_web_sockets(channel_id) + + # add permissions to fetch the instance resource + self.add_policy(self.resource_url, "GET", True) + + def setup_resource(self): + if self.channel_id[0:2] == "WS": + self.resource_url = self.base_url + elif self.channel_id[0:2] == "WK": + self.resource_url = self.base_url + "/Workers/" + self.channel_id + + activity_url = self.base_url + "/Activities" + self.allow(activity_url, "GET") + + reservations_url = self.base_url + "/Tasks/**" + self.allow(reservations_url, "GET") + + elif self.channel_id[0:2] == "WQ": + self.resource_url = self.base_url + \ + "/TaskQueues/" + self.channel_id + + def allow_web_sockets(self, channel_id): + web_socket_url = TASK_ROUTER_BASE_EVENTS_URL + "/" + \ + self.account_sid + "/" + self.channel_id + + self.policies.append(self.make_policy(web_socket_url, "GET", True)) + self.policies.append(self.make_policy(web_socket_url, "POST", True)) + + def validate_jwt(self): + if self.account_sid is None or self.account_sid[0:2] != "AC": + raise ValueError('Invalid AccountSid provided: ' + + self.account_sid) + if self.workspace_sid is None or self.workspace_sid[0:2] != "WS": + raise ValueError('Invalid WorkspaceSid provided: ' + + self.workspace_sid) + if self.channel_id is None: + raise ValueError('ChannelId not provided') + + prefix = self.channel_id[0:2] + if prefix != "WS" and prefix != "WK" and prefix != "WQ": + raise ValueError('Invalid ChannelId provided: ' + self.channel_id) + + def allow_fetch_subresources(self): + self.allow(self.resource_url + "/**", "GET") + + def allow_updates(self): + self.allow(self.resource_url, "POST") + + def allow_updates_subresources(self): + self.allow(self.resource_url + "/**", "POST") + + def allow_delete(self): + self.allow(self.resource_url, "DELETE") + + def allow_delete_subresources(self): + self.allow(self.resource_url + "/**", "DELETE") + + @deprecated + def allow_worker_fetch_attributes(self): + if self.channel_id[0:2] == "WK": + self.policies.append(self.make_policy( + self.resource_url, + 'GET')) + else: + raise ValueError("Deprecated func not applicable to non Worker") + + @deprecated + def allow_worker_activity_updates(self): + if self.channel_id[0:2] == "WK": + self.policies.append(self.make_policy( + self.resource_url, + 'POST', + True, + post_filter={'ActivitySid': REQUIRED})) + else: + raise ValueError("Deprecated func not applicable to non Worker") + + @deprecated + def allow_task_reservation_updates(self): + if self.channel_id[0:2] == "WK": + tasks_url = self.base_url + "/Tasks/**" + self.policies.append(self.make_policy( + tasks_url, + 'POST', + True)) + else: + raise ValueError("Deprecated func not applicable to non Worker") + + def add_policy(self, url, method, + allowed, query_filter=None, post_filter=None): + + policy = self.make_policy(url, method, + allowed, query_filter, post_filter) + self.policies.append(policy) + + def allow(self, url, method, query_filter=None, post_filter=None): + self.add_policy(url, method, True, query_filter, post_filter) + + def deny(self, url, method, query_filter=None, post_filter=None): + self.add_policy(url, method, False, query_filter, post_filter) + + def make_policy(self, url, method, + allowed=True, query_filter=None, post_filter=None): + + # Create a policy dictionary for the given resource and method. + # :param str url: the resource URL to grant or deny access to + # :param str method: the HTTP method to allow or deny + # :param allowed bool: whether this request is allowed + # :param dict query_filter: specific GET parameter names + # to require or allow + # :param dict post_filter: POST parameter names + # to require or allow + + return { + 'url': url, + 'method': method, + 'allow': allowed, + 'query_filter': query_filter or {}, + 'post_filter': post_filter or {} + } + + def get_resource_url(self): + return self.resource_url + + def generate_token(self, ttl=3600): + task_router_attributes = {} + task_router_attributes["account_sid"] = self.account_sid + task_router_attributes["workspace_sid"] = self.workspace_sid + task_router_attributes["channel"] = self.channel_id + + if self.channel_id[0:2] == "WK": + task_router_attributes["worker_sid"] = self.channel_id + elif self.channel_id[0:2] == "WQ": + task_router_attributes["taskqueue_sid"] = self.channel_id + + return self._generate_token(ttl, task_router_attributes) + + def _generate_token(self, ttl, attributes=None): + payload = { + 'iss': self.account_sid, + 'exp': int(time.time()) + ttl, + 'version': TASK_ROUTER_VERSION, + 'friendly_name': self.channel_id, + 'policies': self.policies, + } + + if attributes is not None: + payload.update(attributes) + + return jwt.encode(payload, self.auth_token, 'HS256') + + +class TaskRouterWorkerCapability(TaskRouterCapability): + def __init__(self, account_sid, auth_token, workspace_sid, worker_sid): + super(TaskRouterWorkerCapability, self).__init__(account_sid, + auth_token, + workspace_sid, + worker_sid) + + self.reservations_url = self.base_url + "/Tasks/**" + self.activity_url = self.base_url + "/Activities" + + # add permissions to fetch the list of activities and + # list of worker reservations + self.allow(self.reservations_url, "GET") + self.allow(self.activity_url, "GET") + + def setup_resource(self): + self.resource_url = self.base_url + "/Workers/" + self.channel_id + + def allow_activity_updates(self): + self.policies.append(self.make_policy( + self.resource_url, + 'POST', + True, + post_filter={'ActivitySid': REQUIRED})) + + def allow_reservation_updates(self): + self.policies.append(self.make_policy( + self.reservations_url, + 'POST', + True)) + + +class TaskRouterTaskQueueCapability(TaskRouterCapability): + def __init__(self, account_sid, auth_token, workspace_sid, taskqueue_sid): + super(TaskRouterTaskQueueCapability, self).__init__(account_sid, + auth_token, + workspace_sid, + taskqueue_sid) + + def setup_resource(self): + self.resource_url = self.base_url + "/TaskQueues/" + self.channel_id + + +class TaskRouterWorkspaceCapability(TaskRouterCapability): + def __init__(self, account_sid, auth_token, workspace_sid): + super(TaskRouterWorkspaceCapability, self).__init__(account_sid, + auth_token, + workspace_sid, + workspace_sid) -from .capability import ( - CapabilityAPI, - TaskRouterCapability, - TaskRouterWorkerCapability, - TaskRouterTaskQueueCapability, - TaskRouterWorkspaceCapability -) \ No newline at end of file + def setup_resource(self): + self.resource_url = self.base_url diff --git a/twilio/task_router/capability/capability_api.py b/twilio/task_router/capability/capability_api.py deleted file mode 100644 index af1a9bf4ed..0000000000 --- a/twilio/task_router/capability/capability_api.py +++ /dev/null @@ -1,68 +0,0 @@ -import time - -from .. import jwt - - -class CapabilityAPI(object): - """ - A token to control permissions for the TaskRouter service. - - :param str account_sid: The account to generate a token for - :param str auth_token: The auth token for the account. Used to sign the - token and will not be included in generated output. - :param str workspace_sid: The workspace to grant capabilities over - :param str worker_sid: The worker sid to grant capabilities over - :param str base_url: The base TaskRouter API URL - :param str base_ws_url: The base TaskRouter event stream URL - """ - def __init__(self, account_sid, auth_token, version, friendly_name): - self.account_sid = account_sid - self.auth_token = auth_token - - self.version = version - self.friendly_name = friendly_name; - self.policies = [] - - def add_policy(self, url, method, allowed, query_filter = None, post_filter = None): - policy = self.make_policy(url, method, allowed, query_filter, post_filter) - self.policies.append(policy) - - def allow(self, url, method, query_filter = None, post_filter = None): - self.add_policy(url, method, True, query_filter, post_filter) - - def deny(self, url, method, query_filter = None, post_filter = None): - self.add_policy(url, method, False, query_filter, post_filter) - - def generate_token(self, ttl = 3600, attributes = None): - return self._generate_token(ttl) - - def _generate_token(self, ttl, attributes=None): - payload = { - 'iss': self.account_sid, - 'exp': int(time.time()) + ttl, - 'version': self.version, - 'friendly_name': self.friendly_name, - 'policies': self.policies, - } - - if attributes is not None: - payload.update(attributes) - - return jwt.encode(payload, self.auth_token, 'HS256') - - def make_policy(self, url, method, allowed = True, query_filter = None, post_filter = None): - # Create a policy dictionary for the given resource and method. - - # :param str url: the resource URL to grant or deny access to - # :param str method: the HTTP method to allow or deny - # :param allowed bool: whether this request is allowed - # :param dict query_filter: specific GET parameter names to require or allow - # :param dict post_filter: POST parameter names to require or allow - - return { - 'url': url, - 'method': method, - 'allowed': allowed, - 'query_filter': query_filter or {}, - 'post_filter': post_filter or {} - } \ No newline at end of file diff --git a/twilio/task_router/capability/task_router_capability.py b/twilio/task_router/capability/task_router_capability.py deleted file mode 100644 index 6758020af6..0000000000 --- a/twilio/task_router/capability/task_router_capability.py +++ /dev/null @@ -1,187 +0,0 @@ -from .capability_api import CapabilityAPI - -import warnings -warnings.simplefilter('always', DeprecationWarning) - -TASK_ROUTER_BASE_URL = 'https://taskrouter.twilio.com' -TASK_ROUTER_BASE_EVENTS_URL = 'https://event-bridge.twilio.com/v1/wschannels' -TASK_ROUTER_VERSION = "v1" - -REQUIRED = {'required': True} -OPTIONAL = {'required': False} - -def deprecated(func): - def log_warning(*args, **kwargs): - # stacklevel = 2 makes the warning refer to the caller of the deprecation rather than the source of deprecation itself - warnings.warn("Call to deprecated function {}.".format(func.__name__), stacklevel = 2, category = DeprecationWarning) - return func(*args, **kwargs) - return log_warning - - -class TaskRouterCapability(CapabilityAPI): - def __init__(self, account_sid, auth_token, workspace_sid, channel_id): - super(TaskRouterCapability, self).__init__(account_sid, auth_token, TASK_ROUTER_VERSION, channel_id) - - self.workspace_sid = workspace_sid - self.channel_id = channel_id - self.base_url = TASK_ROUTER_BASE_URL + "/" + TASK_ROUTER_VERSION + "/Workspaces/" + workspace_sid - - # validate the JWT - self.validate_jwt() - - # set up resources - self.setup_resource() - - # add permissions to GET and POST to the event-bridge channel - self.allow_web_sockets(channel_id) - - # add permissions to fetch the instance resource - self.add_policy(self.resource_url, "GET", True) - - def setup_resource(self): - if self.channel_id[0:2] == "WS": - self.resource_url = self.base_url - elif self.channel_id[0:2] == "WK": - self.resource_url = self.base_url + "/Workers/" + self.channel_id - - activity_url = self.base_url + "/Activities" - self.allow(activity_url, "GET") - - reservations_url = self.base_url + "/Tasks/**" - self.allow(reservations_url, "GET") - - elif self.channel_id[0:2] == "WQ": - self.resource_url = self.base_url + "/TaskQueues/" + self.channel_id - - def allow_web_sockets(self, channel_id): - web_socket_url = TASK_ROUTER_BASE_EVENTS_URL + "/" + self.account_sid + "/" + self.channel_id; - self.policies.append(self.make_policy(web_socket_url, "GET", True)) - self.policies.append(self.make_policy(web_socket_url, "POST", True)) - - def validate_jwt(self): - if self.account_sid is None or self.account_sid[0:2] != "AC": - raise ValueError('Invalid AccountSid provided: ' + self.account_sid) - if self.workspace_sid is None or self.workspace_sid[0:2] != "WS": - raise ValueError('Invalid WorkspaceSid provided: ' + self.workspace_sid) - if self.channel_id is None: - raise ValueError('ChannelId not provided') - - prefix = self.channel_id[0:2] - if prefix != "WS" and prefix != "WK" and prefix != "WQ": - raise ValueError('Invalid ChannelId provided: ' + self.channel_id) - - def allow_fetch_subresources(self): - self.allow(self.resource_url + "/**", "GET") - - def allow_updates(self): - self.allow(self.resource_url, "POST") - - def allow_updates_subresources(self): - self.allow(self.resource_url + "/**", "POST") - - def allow_delete(self): - self.allow(self.resource_url, "DELETE") - - def allow_delete_subresources(self): - self.allow(self.resource_url + "/**", "DELETE") - - @deprecated - def allow_worker_fetch_attributes(self): - if self.channel_id[0:2] == "WK": - self.policies.append(self.make_policy( - self.resource_url, - 'GET' - ) - ) - else: - raise ValueError("Deprecated function not applicable to non Worker") - - @deprecated - def allow_worker_activity_updates(self): - if self.channel_id[0:2] == "WK": - self.policies.append(self.make_policy( - self.resource_url, - 'POST', - True, - post_filter = {'ActivitySid': REQUIRED} - ) - ) - else: - raise ValueError("Deprecated function not applicable to non Worker") - - - @deprecated - def allow_task_reservation_updates(self): - if self.channel_id[0:2] == "WK": - tasks_url = self.base_url + "/Tasks/**" - self.policies.append(self.make_policy( - tasks_url, - 'POST', - True, - ) - ) - else: - raise ValueError("Deprecated function not applicable to non Worker") - - def get_resource_url(self): - return self.resource_url - - def generate_token(self, ttl = 3600): - task_router_attributes = {} - task_router_attributes["account_sid"] = self.account_sid - task_router_attributes["workspace_sid"] = self.workspace_sid - task_router_attributes["channel"] = self.channel_id - - if self.channel_id[0:2] == "WK": - task_router_attributes["worker_sid"] = self.channel_id - elif self.channel_id[0:2] == "WQ": - task_router_attributes["taskqueue_sid"] = self.channel_id - - return self._generate_token(ttl, task_router_attributes) - -class TaskRouterWorkerCapability(TaskRouterCapability): - def __init__(self, account_sid, auth_token, workspace_sid, worker_sid): - super(TaskRouterWorkerCapability, self).__init__(account_sid, auth_token, workspace_sid, worker_sid) - - self.reservations_url = self.base_url + "/Tasks/**" - self.activity_url = self.base_url + "/Activities" - - # add permissions to fetch the list of activities and list of worker reservations - self.allow(self.reservations_url, "GET") - self.allow(self.activity_url, "GET") - - def setup_resource(self): - self.resource_url = self.base_url + "/Workers/" + self.channel_id - - def allow_activity_updates(self): - self.policies.append(self.make_policy( - self.resource_url, - 'POST', - True, - post_filter = {'ActivitySid': REQUIRED} - ) - ) - def allow_reservation_updates(self): - self.policies.append(self.make_policy( - self.reservations_url, - 'POST', - True - ) - ) - -class TaskRouterTaskQueueCapability(TaskRouterCapability): - def __init__(self, account_sid, auth_token, workspace_sid, taskqueue_sid): - super(TaskRouterTaskQueueCapability, self).__init__(account_sid, auth_token, workspace_sid, taskqueue_sid) - - def setup_resource(self): - self.resource_url = self.base_url + "/TaskQueues/" + self.channel_id - - -class TaskRouterWorkspaceCapability(TaskRouterCapability): - def __init__(self, account_sid, auth_token, workspace_sid): - super(TaskRouterWorkspaceCapability, self).__init__(account_sid, auth_token, workspace_sid, workspace_sid) - - def setup_resource(self): - self.resource_url = self.base_url - - From c53d711d126013a2e93f2929128a0f358dfda566 Mon Sep 17 00:00:00 2001 From: gramanathaiah Date: Thu, 23 Jul 2015 14:03:53 -0700 Subject: [PATCH 07/42] Helper libs for workflow construction --- docs/usage/taskrouter.rst | 369 +++++++++++++++++- tests/task_router/test_workflow_config.py | 59 +++ twilio/rest/resources/__init__.py | 4 + twilio/rest/resources/task_router/__init__.py | 16 + .../task_router/taskrouter_config.py | 24 ++ .../resources/task_router/workflow_config.py | 32 ++ .../resources/task_router/workflow_rule.py | 43 ++ .../task_router/workflow_ruletarget.py | 43 ++ 8 files changed, 588 insertions(+), 2 deletions(-) create mode 100644 tests/task_router/test_workflow_config.py create mode 100644 twilio/rest/resources/task_router/taskrouter_config.py create mode 100644 twilio/rest/resources/task_router/workflow_config.py create mode 100644 twilio/rest/resources/task_router/workflow_rule.py create mode 100644 twilio/rest/resources/task_router/workflow_ruletarget.py diff --git a/docs/usage/taskrouter.rst b/docs/usage/taskrouter.rst index 5915a4744e..76c095a9e9 100644 --- a/docs/usage/taskrouter.rst +++ b/docs/usage/taskrouter.rst @@ -38,6 +38,39 @@ its unique ID. ) print workspace.sid +.. + +The following code will create a update an existing :class:`Workspace` resource + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + workspace = client.workspaces.update( + WORKSPACE_SID, + friendly_name='Test Workspace', + event_callback_uri="http://www.example.com", + template='FIFO') + +.. +The following code will delete an existing :class:`workspace` resource + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + client.workspaces.delete(WORKSPACE_SID) +.. Workflows --------- @@ -87,13 +120,97 @@ unique ID: client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) - workspace = client.workflows(WORKSPACE_SID).create( + workflow = client.workflows(WORKSPACE_SID).create( friendly_name="Incoming Call Flow", assignment_callback_url="https://example.com/callback", fallback_assignment_callback_url="https://example.com/callback2", configuration=CONFIG ) - print workspace.sid + print workflow.sid + +.. + +The following code will update an existing :class:`workflow` resource + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + # Some JSON to configure the Workflow. See the documentation at + # http://www.twilio.com/docs/taskrouter for more details. + CONFIG = """ + { + "task_routing":{ + "filters":[ + { + "friendly_name":"Gold Tickets", + "expression":"customer_value == 'Gold' AND type == 'ticket'", + "targets":[ + { + "task_queue_sid":"WQ0123456789abcdef0123456789abcdef", + "priority":"2" + } + ] + }, + { + "targets": [ + { + "queue": "WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f", + "priority": "1" + } + ], + "friendly_name": "Marketing", + "expression": "type == 'marketing'" + } + ], + "default_filter":{ + "task_queue_sid":"WQabcdef01234567890123456789abcdef" + } + } + } + """ + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + workflow = client.workflows(WORKSPACE_SID).update( + WORKFLOW_SID, + friendly_name="Incoming Call Flow", + assignment_callback_url="https://example.com/callback", + fallback_assignment_callback_url="https://example.com/callback2", + configuration=CONFIG + ) + print workflow.sid + +.. + +The following code will delete an existing :class:`Workflow` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + client.workflows(WORKSPACE_SID).delete( + WORKFLOW_SID + ) + + +.. Activities @@ -122,6 +239,49 @@ To create a new :class:`Activity`: available=False, # Whether workers are available to handle tasks during this activity ) +.. + +To update an existing :class:`Activity` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + activity = client.activities(WORKSPACE_SID).update( + ACTIVITY_SID, + friendly_name="Coffee Break", + available=True, + ) + +.. + +To delete an existing :class:`Activity` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + activity = client.activities(WORKSPACE_SID).delete( + ACTIVITY_SID + ) + +.. Workers ------- @@ -153,6 +313,54 @@ To create a new :class:`Worker`: ) print worker.sid +.. + +To update an existing :class:`Worker` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + worker = client.workers(WORKSPACE_SID).update( + WORKER_SID, + friendly_name="Jamie Howe", + attributes="""{ + "phone": "+14155551234", + "languages": ["EN", "ES","DE"] + } + """ + ) + print worker.sid + +.. + +To delete an exisitng :class:`Worker` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + client.workers(WORKSPACE_SID).delete( + WORKER_SID + ) + +.. TaskQueues ---------- @@ -186,6 +394,57 @@ To create a new :class:`TaskQueue`: ) print queue.sid +.. + +To update an existing :class:`TaskQueue` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + queue = client.task_queues(WORKSPACE_SID).update( + TASKQUEUE_SID, + friendly_name="Sales+Pre-Sales", + # The Activity to assign workers when a task is reserved for them + reservation_activity_sid="WA11111111111", + # The Activity to assign workers when a task is assigned to them + assignment_activity_sid="WA222222222222", + ) + print queue.sid + +.. + +To delete an existing :class:`TaskQueue` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + queue = client.task_queues(WORKSPACE_SID).delete( + TASKQUEUE_SID + ) + print queue.sid + +.. + Tasks ----- @@ -223,3 +482,109 @@ To create a new :class:`Task` via the REST API: workflow_sid=WORKFLOW_SID ) print task.sid +.. + +To update an exisiting :class:`Task` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + WORKFLOW_SID = "WWXXXXXXXXXXXXXX" + # Some JSON containing attributes for this task. User-defined. + TASK_ATTRIBUTES = """{ + "type": "call", + "contact": "+2014068777", + "customer-value": "gold", + "task-reason": "support", + "callSid": "CA42ed11..." + }""" + + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + task = client.tasks(WORKSPACE_SID).update( + TASK_SID, + attributes=TASK_ATTRIBUTES, + assignment_status='pending', + workflow_sid=WORKFLOW_SID + ) + print task.sid +.. + +To delete an exisitng :class:`Task` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + WORKFLOW_SID = "WWXXXXXXXXXXXXXX" + # Some JSON containing attributes for this task. User-defined. + TASK_ATTRIBUTES = """{ + "type": "call", + "contact": "+2014068777", + "customer-value": "gold", + "task-reason": "support", + "callSid": "CA42ed11..." + }""" + + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + client.tasks(WORKSPACE_SID).delete( + TASK_SID + ) + +.. + + +Using Workflow builder helper classes to create a :class:`Workflow` resource. + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + rules =[] + ruleTargets=[] + anotherRuleTargets=[] + ruleTarget = WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7","1==1",1,20) + anotherRuleTarget= WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da","1==1",1,210) + ruleTargets.append(ruleTarget); + anotherRuleTargets.append(anotherRuleTarget); + rule = WorkflowRule("1==1",ruleTargets,"SomeQ") + rules.append(rule) + anotherRule = WorkflowRule("1==1",ruleTargets1,"SomeOtherQ") + rules.append(anotherRule); + defaultTarget = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916","1==1",None,None) + config = WorkflowConfig(rules,defaultTarget) + print config.toJson() + + workflow = client.workflows(WORKSPACE_SID).create( + friendly_name="Incoming Call Flow", + assignment_callback_url="https://example.com/callback", + fallback_assignment_callback_url="https://example.com/callback2", + configuration=config.toJson() + ) + + print workflow.sid + + + +.. \ No newline at end of file diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py new file mode 100644 index 0000000000..cb2e772e03 --- /dev/null +++ b/tests/task_router/test_workflow_config.py @@ -0,0 +1,59 @@ +import unittest +import json + +from mock import patch, Mock + + + + +from tests.tools import create_mock_json +from twilio.rest.resources.task_router.workflow_config import WorkflowConfig +from twilio.rest.resources.task_router.workflow_rule import WorkflowRule +from twilio.rest.resources.task_router.workflow_ruletarget import WorkflowRuleTarget + +class WorkflowConfigTest(unittest.TestCase): + def test_to_json(self): + rules =[] + ruleTargets=[] + ruleTargets1=[] + ruleTarget = WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7","1==1",1,20) + ruleTarget1 = WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da","1==1",1,210) + ruleTargets.append(ruleTarget); + ruleTargets1.append(ruleTarget1); + rule = WorkflowRule("1==1",ruleTargets,"SomeQ") + rules.append(rule) + rule1 = WorkflowRule("1==1",ruleTargets1,"SomeOtherQ") + rules.append(rule1) + deftarget = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916","1==1",None,None) + + + config = WorkflowConfig(rules,deftarget) + self.assertEqual(self.is_json(config.toJson()),True) + + + + + def test_from_Json(self): + + data="{\"task_routing\": { \"filters\": [ { \"targets\": [ { \"queue\": \"WQec62de0e1148b8477f2e24579779c8b1\", \"expression\": \"task.language IN worker.languages\" } ], \"friendly_name\": \"Sales\", \"expression\": \"type == \\\"sales\\\"\" }, { \"targets\": [ { \"queue\": \"WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f\", \"expression\": \"task.language IN worker.languages\" } ], \"friendly_name\": \"Marketing\", \"expression\": \"type == \\\"marketing\\\"\" }, { \"targets\": [ { \"queue\": \"WQe5eb317eb23500ade45087ea6522896c\", \"expression\": \"task.language IN worker.languages\" } ], \"friendly_name\": \"Support\", \"expression\": \"type == \\\"support\\\"\" } ], \"default_filter\": { \"queue\": \"WQ05f810d2d130344fd56e3c91ece2e594\" } }}" + config = WorkflowConfig.json2obj(data) + self.assertEqual(len(config.task_routing.filters),3) + self.assertEqual(len(config.task_routing.default_filter),1) + + + + def test_from_json2(self): + data ="{ \"task_routing\": { \"default_filter\": { \"expression\": null, \"priority\": null, \"queue\": \"WQYYYYY\", \"timeout\": null }, \"filters\": [ { \"expression\": \"1==1\", \"friendly_name\": \"SomeQ\", \"targets\": [ { \"expression\": \"1==1\", \"priority\": 1, \"queue\": \"WQXXXX\", \"timeout\": 20 } ] }, { \"expression\": \"1==1\", \"friendly_name\": \"SomeOtherQ\", \"targets\": [ { \"expression\": \"1==1\", \"priority\": 1, \"queue\": \"WQXXXX\", \"timeout\": 20 } ] } ] }}" + config = WorkflowConfig.json2obj(data) + self.assertEqual(len(config.task_routing.filters),2) + self.assertEqual(len(config.task_routing.default_filter),4) + + + + def is_json(self,myjson): + try: + json_object = json.loads(myjson) + except ValueError, e: + return False + return True + diff --git a/twilio/rest/resources/__init__.py b/twilio/rest/resources/__init__.py index fa9aef58ec..8a6ecc70c6 100644 --- a/twilio/rest/resources/__init__.py +++ b/twilio/rest/resources/__init__.py @@ -55,12 +55,16 @@ Reservations, Task, Tasks, + TaskRouterConfig, TaskQueue, TaskQueues, Worker, Workers, Workflow, Workflows, + WorkflowConfig, + WorkflowRule, + WorkflowRuleTarget, Workspace, Workspaces, ) diff --git a/twilio/rest/resources/task_router/__init__.py b/twilio/rest/resources/task_router/__init__.py index e312f1a882..61d786781e 100644 --- a/twilio/rest/resources/task_router/__init__.py +++ b/twilio/rest/resources/task_router/__init__.py @@ -36,3 +36,19 @@ Workspace, Workspaces ) + + +from .taskrouter_config import ( + TaskRouterConfig +) + +from .workflow_config import ( + WorkflowConfig +) + +from .workflow_ruletarget import ( + WorkflowRuleTarget +) +from .workflow_rule import ( + WorkflowRule +) diff --git a/twilio/rest/resources/task_router/taskrouter_config.py b/twilio/rest/resources/task_router/taskrouter_config.py new file mode 100644 index 0000000000..67c12813de --- /dev/null +++ b/twilio/rest/resources/task_router/taskrouter_config.py @@ -0,0 +1,24 @@ +from .workflow_rule import WorkflowRule +from .workflow_ruletarget import WorkflowRuleTarget +class TaskRouterConfig: + + """ + TaskRouterConfig represents the filter and default_filter + of a workflow configuration of taskrouter + """ + + def __init__(self, rules, defaultTarget): + self.filters = rules + self.default_filter = defaultTarget + + @property + def filters(self): + return self.filters + + @property + def defaultFilter(self): + return self.default_filter + + def __repr__(self): + out = self.__dict__ + return out diff --git a/twilio/rest/resources/task_router/workflow_config.py b/twilio/rest/resources/task_router/workflow_config.py new file mode 100644 index 0000000000..144f3e36ab --- /dev/null +++ b/twilio/rest/resources/task_router/workflow_config.py @@ -0,0 +1,32 @@ +from .taskrouter_config import TaskRouterConfig +import json +from collections import namedtuple, Iterable, OrderedDict +import numpy as np +from twilio.rest.resources.task_router.workflow_rule import WorkflowRule + + +class WorkflowConfig: + + """ + WorkflowConfig represents the whole workflow config json which contains + filters and default_filter. + """ + + def __init__(self, workflowRules, defaultTarget): + #filters and default_filters + self.task_routing = TaskRouterConfig(workflowRules, defaultTarget) + + + @property + def taskrouterConfig(self): + return self.task_routing + + def toJson(self): + return json.dumps(self, default=lambda o: o.__dict__, + sort_keys=True, indent=4) + + + @staticmethod + def json2obj(data): + m=json.loads(data) + return WorkflowConfig(m['task_routing']['filters'],m['task_routing']['default_filter']) diff --git a/twilio/rest/resources/task_router/workflow_rule.py b/twilio/rest/resources/task_router/workflow_rule.py new file mode 100644 index 0000000000..fe16c93029 --- /dev/null +++ b/twilio/rest/resources/task_router/workflow_rule.py @@ -0,0 +1,43 @@ +from .workflow_ruletarget import WorkflowRuleTarget +class WorkflowRule: + """ + WorkflowRule represents the top level filter + which contains a 1 or more targets + + ..attribute::expression + + The expression at the top level filter + + ..attribute::targets + + The list of targets under the filter + + ..attribute::friendlyName + + The name of the filter + """ + _targets = list() + def __init__(self, expression, targets, friendlyName): + + self.expression = expression + self.targets = targets + self.friendly_name = friendlyName + + @property + def expression(self): + return self.expression + + @property + def targets(self): + return self.targets + + @property + def friendlyName(self): + return self.friendly_name + + def __repr__(self): + out = dict() + out['expression'] = self.expression + out['friendlyName'] = self.friendly_name + out['target'] = self.targets + return str(out) diff --git a/twilio/rest/resources/task_router/workflow_ruletarget.py b/twilio/rest/resources/task_router/workflow_ruletarget.py new file mode 100644 index 0000000000..dd1da9d13a --- /dev/null +++ b/twilio/rest/resources/task_router/workflow_ruletarget.py @@ -0,0 +1,43 @@ +class WorkflowRuleTarget: + """ + Workflow Rule target which is encompassed + inside targets + + ..attribute::queue + + The queue which will handle the task matching this filter target + + ..attribute::expression + + The dynamic expression if any for this matching + + ..attribute::priority + + The priority for the target + + ..attribute::timeout + + The timeout before the reservation expires. + """ + def __init__(self, queue, expression, priority, timeout): + + self.queue = queue + self.expression = expression + self.priority = priority + self.timeout = timeout + + @property + def queue(self): + return self.queue + + @property + def expression(self): + return self.expression + + @property + def priority(self): + return self.priority + + @property + def timeout(self): + return self.timeout From 15db38a83c78372eb8d1c32956d50cfb919363e9 Mon Sep 17 00:00:00 2001 From: Jen Li Date: Tue, 28 Jul 2015 13:45:18 -0700 Subject: [PATCH 08/42] added helper function --- tests/task_router/test_capability.py | 18 +- .../test_task_router_capability.py | 164 +++++------------- 2 files changed, 46 insertions(+), 136 deletions(-) diff --git a/tests/task_router/test_capability.py b/tests/task_router/test_capability.py index 707fe97f83..eb4d15edba 100644 --- a/tests/task_router/test_capability.py +++ b/tests/task_router/test_capability.py @@ -55,21 +55,18 @@ def test_defaults(self): self.assertTrue(decoded is not None) websocket_url = ( - 'https://event-bridge.twilio.com/v1/wschannels/%s/%s' % - (self.account_sid, self.worker_sid) + 'https://event-bridge.twilio.com/v1/wschannels/{}/{}'.format(self.account_sid, self.worker_sid) ) expected = [ { - 'url': - 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities', + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities', 'method': 'GET', 'allow': True, 'query_filter': {}, 'post_filter': {}, }, { - 'url': 'https://taskrouter.twilio.com/v1/Workspaces/%s/Tasks/**' % - (self.workspace_sid), + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/{}/Tasks/**'.format(self.workspace_sid), 'method': 'GET', 'allow': True, 'query_filter': {}, @@ -90,8 +87,7 @@ def test_defaults(self): 'post_filter': {}, }, { - 'url': 'https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s' % - (self.workspace_sid, self.worker_sid), + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/{}/Workers/{}'.format(self.workspace_sid, self.worker_sid), 'method': 'GET', 'allow': True, 'query_filter': {}, @@ -106,7 +102,7 @@ def test_allow_worker_activity_updates(self): decoded = jwt.decode(token, self.auth_token) self.assertTrue(decoded is not None) - url = 'https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s' % ( + url = 'https://taskrouter.twilio.com/v1/Workspaces/{}/Workers/{}'.format( self.workspace_sid, self.worker_sid, ) @@ -126,7 +122,7 @@ def test_allow_worker_fetch_attributes(self): decoded = jwt.decode(token, self.auth_token) self.assertTrue(decoded is not None) - url = 'https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s' % ( + url = 'https://taskrouter.twilio.com/v1/Workspaces/{}/Workers/{}'.format( self.workspace_sid, self.worker_sid, ) @@ -147,7 +143,7 @@ def test_allow_task_reservation_updates(self): decoded = jwt.decode(token, self.auth_token) self.assertTrue(decoded is not None) - url = 'https://taskrouter.twilio.com/v1/Workspaces/%s/Tasks/**' % ( + url = 'https://taskrouter.twilio.com/v1/Workspaces/{}/Tasks/**'.format( self.workspace_sid, ) diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py index 9cbe90c1aa..8c9cdb3131 100644 --- a/tests/task_router/test_task_router_capability.py +++ b/tests/task_router/test_task_router_capability.py @@ -1,3 +1,6 @@ +import sys +sys.path.append('/Users/wli/Projects/python-private/twilio/') + import unittest import warnings @@ -7,6 +10,14 @@ class TaskRouterCapabilityTest(unittest.TestCase): + def check_policy(self, method, url, policy): + print policy + self.assertEqual(url, policy['url']) + self.assertEqual(method, policy['method']) + self.assertTrue(policy['allow']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) + def test_workspace_default(self): account_sid = "AC123" auth_token = "foobar" @@ -32,29 +43,12 @@ def test_workspace_default(self): policies = decoded['policies'] self.assertEqual(len(policies), 3) - # websocket GET - get_policy = policies[0] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", get_policy['url']) - self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allow']) - self.assertEqual({}, get_policy['query_filter']) - self.assertEqual({}, get_policy['post_filter']) - - # websocket POST - post_policy = policies[1] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", post_policy['url']) - self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allow']) - self.assertEqual({}, post_policy['query_filter']) - self.assertEqual({}, post_policy['post_filter']) - - # fetch GET - fetch_policy = policies[2] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456", fetch_policy['url']) - self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allow']) - self.assertEqual({}, fetch_policy['query_filter']) - self.assertEqual({}, fetch_policy['post_filter']) + for method, url, policy in [ + ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", policies[0]), + ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", policies[1]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456", policies[2]), + ]: + yield self.check_policy, method, url, policy def test_worker_default(self): account_sid = "AC123" @@ -82,45 +76,14 @@ def test_worker_default(self): policies = decoded['policies'] self.assertEqual(len(policies), 5) - # activity GET - fetch_activity = policies[0] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", fetch_activity['url']) - self.assertEqual("GET", fetch_activity['method']) - self.assertTrue(fetch_activity['allow']) - self.assertEqual({}, fetch_activity['query_filter']) - self.assertEqual({}, fetch_activity['post_filter']) - - # reservation GET - fetch_reservation = policies[1] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", fetch_reservation['url']) - self.assertEqual("GET", fetch_reservation['method']) - self.assertTrue(fetch_reservation['allow']) - self.assertEqual({}, fetch_reservation['query_filter']) - self.assertEqual({}, fetch_reservation['post_filter']) - - # websocket GET - get_policy = policies[2] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", get_policy['url']) - self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allow']) - self.assertEqual({}, get_policy['query_filter']) - self.assertEqual({}, get_policy['post_filter']) - - # websocket POST - post_policy = policies[3] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", post_policy['url']) - self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allow']) - self.assertEqual({}, post_policy['query_filter']) - self.assertEqual({}, post_policy['post_filter']) - - # fetch GET - fetch_policy = policies[4] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", fetch_policy['url']) - self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allow']) - self.assertEqual({}, fetch_policy['query_filter']) - self.assertEqual({}, fetch_policy['post_filter']) + for method, url, policy in [ + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", policies[0]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", policies[1]), + ('GET', "https://taskrouter.twilio.com/v1/wschannels/AC123/WK789", policies[2]), + ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[3]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", policies[4]) + ]: + yield self.check_policy, method, url, policy def test_task_queue_default(self): account_sid = "AC123" @@ -148,29 +111,12 @@ def test_task_queue_default(self): policies = decoded['policies'] self.assertEqual(len(policies), 3) - # websocket GET - get_policy = policies[0] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", get_policy['url']) - self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allow']) - self.assertEqual({}, get_policy['query_filter']) - self.assertEqual({}, get_policy['post_filter']) - - # websocket POST - post_policy = policies[1] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", post_policy['url']) - self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allow']) - self.assertEqual({}, post_policy['query_filter']) - self.assertEqual({}, post_policy['post_filter']) - - # fetch GET - fetch_policy = policies[2] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789", fetch_policy['url']) - self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allow']) - self.assertEqual({}, fetch_policy['query_filter']) - self.assertEqual({}, fetch_policy['post_filter']) + for method, url, policy in [ + ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", policies[0]), + ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", policies[1]) + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789", policies[2]) + ]: + yield self.check_policy, method, url, policy def test_deprecated_worker(self): account_sid = "AC123" @@ -199,46 +145,14 @@ def test_deprecated_worker(self): self.assertEqual(len(policies), 5) # should expect 5 policies - - # activity GET - fetch_activity = policies[0] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", fetch_activity['url']) - self.assertEqual("GET", fetch_activity['method']) - self.assertTrue(fetch_activity['allow']) - self.assertEqual({}, fetch_activity['query_filter']) - self.assertEqual({}, fetch_activity['post_filter']) - - # reservation GET - fetch_reservation = policies[1] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", fetch_reservation['url']) - self.assertEqual("GET", fetch_reservation['method']) - self.assertTrue(fetch_reservation['allow']) - self.assertEqual({}, fetch_reservation['query_filter']) - self.assertEqual({}, fetch_reservation['post_filter']) - - # websocket GET - get_policy = policies[2] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", get_policy['url']) - self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allow']) - self.assertEqual({}, get_policy['query_filter']) - self.assertEqual({}, get_policy['post_filter']) - - # websocket POST - post_policy = policies[3] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", post_policy['url']) - self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allow']) - self.assertEqual({}, post_policy['query_filter']) - self.assertEqual({}, post_policy['post_filter']) - - # fetch GET - fetch_policy = policies[4] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", fetch_policy['url']) - self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allow']) - self.assertEqual({}, fetch_policy['query_filter']) - self.assertEqual({}, fetch_policy['post_filter']) + for method, url, policy in [ + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", policies[0]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", policies[1]), + ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[2]), + ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[3]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", policies[4]) + ]: + yield self.check_policy, method, url, policy # check deprecated warnings with warnings.catch_warnings(record=True) as w: From 3aaae2451de6244158479faabdbb0b56b46b2d52 Mon Sep 17 00:00:00 2001 From: Jen Li Date: Tue, 28 Jul 2015 15:48:22 -0700 Subject: [PATCH 09/42] added helper function, formatting --- .../test_task_router_capability.py | 48 ++++------ .../test_task_router_worker_capability.py | 96 +++++++------------ .../test_task_router_workspace_capability.py | 73 +++++++------- twilio/task_router/__init__.py | 58 +++++------ 4 files changed, 118 insertions(+), 157 deletions(-) diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py index 8c9cdb3131..7d1a99c644 100644 --- a/tests/task_router/test_task_router_capability.py +++ b/tests/task_router/test_task_router_capability.py @@ -1,5 +1,6 @@ import sys -sys.path.append('/Users/wli/Projects/python-private/twilio/') +sys.path.append('../../') +sys.path.append('/Library/Python/2.7/site-packages') import unittest import warnings @@ -11,13 +12,25 @@ class TaskRouterCapabilityTest(unittest.TestCase): def check_policy(self, method, url, policy): - print policy self.assertEqual(url, policy['url']) self.assertEqual(method, policy['method']) self.assertTrue(policy['allow']) self.assertEqual({}, policy['query_filter']) self.assertEqual({}, policy['post_filter']) + def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): + self.assertEqual(decoded["iss"], account_sid) + self.assertEqual(decoded["account_sid"], account_sid) + self.assertEqual(decoded["workspace_sid"], workspace_sid) + self.assertEqual(decoded["channel"], channel_id) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], channel_id) + + if 'worker_sid' in decoded.keys(): + self.assertEqual(decoded['worker_sid'], channel_sid) + if 'taskqueue_sid' in decoded.keys(): + self.assertEqual(decoded['taskqueue_sid'], channel_sid) + def test_workspace_default(self): account_sid = "AC123" auth_token = "foobar" @@ -33,12 +46,7 @@ def test_workspace_default(self): decoded = jwt.decode(token, auth_token) self.assertIsNotNone(decoded) - self.assertEqual(decoded["iss"], account_sid) - self.assertEqual(decoded["account_sid"], account_sid) - self.assertEqual(decoded["workspace_sid"], workspace_sid) - self.assertEqual(decoded["channel"], channel_id) - self.assertEqual(decoded["version"], "v1") - self.assertEqual(decoded["friendly_name"], channel_id) + self.check_decoded(decoded, account_sid, workspace_sid, channel_id) policies = decoded['policies'] self.assertEqual(len(policies), 3) @@ -65,13 +73,7 @@ def test_worker_default(self): decoded = jwt.decode(token, auth_token) self.assertIsNotNone(decoded) - self.assertEqual(decoded["iss"], account_sid) - self.assertEqual(decoded["account_sid"], account_sid) - self.assertEqual(decoded["workspace_sid"], workspace_sid) - self.assertEqual(decoded["worker_sid"], worker_sid) - self.assertEqual(decoded["channel"], worker_sid) - self.assertEqual(decoded["version"], "v1") - self.assertEqual(decoded["friendly_name"], worker_sid) + self.check_decoded(decoded, account_sid, workspace_sid, channel_id, worker_sid) policies = decoded['policies'] self.assertEqual(len(policies), 5) @@ -100,13 +102,7 @@ def test_task_queue_default(self): decoded = jwt.decode(token, auth_token) self.assertIsNotNone(decoded) - self.assertEqual(decoded["iss"], account_sid) - self.assertEqual(decoded["account_sid"], account_sid) - self.assertEqual(decoded["workspace_sid"], workspace_sid) - self.assertEqual(decoded["taskqueue_sid"], taskqueue_sid) - self.assertEqual(decoded["channel"], taskqueue_sid) - self.assertEqual(decoded["version"], "v1") - self.assertEqual(decoded["friendly_name"], taskqueue_sid) + self.check_decoded(decoded, account_sid, workspace_sid, channel_id, taskqueue_sid) policies = decoded['policies'] self.assertEqual(len(policies), 3) @@ -133,13 +129,7 @@ def test_deprecated_worker(self): decoded = jwt.decode(token, auth_token) self.assertIsNotNone(decoded) - self.assertEqual(decoded["iss"], account_sid) - self.assertEqual(decoded["account_sid"], account_sid) - self.assertEqual(decoded["workspace_sid"], workspace_sid) - self.assertEqual(decoded["worker_sid"], worker_sid) - self.assertEqual(decoded["channel"], worker_sid) - self.assertEqual(decoded["version"], "v1") - self.assertEqual(decoded["friendly_name"], worker_sid) + self.check_decoded(decoded, account_sid, workspace_sid, channel_id, worker_sid) policies = decoded['policies'] self.assertEqual(len(policies), 5) diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py index 35abb5b831..e2ca7a2cd9 100644 --- a/tests/task_router/test_task_router_worker_capability.py +++ b/tests/task_router/test_task_router_worker_capability.py @@ -1,3 +1,7 @@ +import sys +sys.path.append('../../') +sys.path.append('/Library/Python/2.7/site-packages') + import time import unittest @@ -6,6 +10,25 @@ class TaskRouterWorkerCapabilityTest(unittest.TestCase): + def check_policy(self, method, url, policy): + self.assertEqual(url, policy['url']) + self.assertEqual(method, policy['method']) + self.assertTrue(policy['allow']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) + + def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): + self.assertEqual(decoded["iss"], account_sid) + self.assertEqual(decoded["account_sid"], account_sid) + self.assertEqual(decoded["workspace_sid"], workspace_sid) + self.assertEqual(decoded["channel"], channel_id) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], channel_id) + + if 'worker_sid' in decoded.keys(): + self.assertEqual(decoded['worker_sid'], channel_sid) + if 'taskqueue_sid' in decoded.keys(): + self.assertEqual(decoded['taskqueue_sid'], channel_sid) def setUp(self): self.account_sid = "AC123" @@ -22,13 +45,7 @@ def test_generate_token(self): decoded = jwt.decode(token, self.auth_token) self.assertIsNotNone(decoded) - self.assertEqual(decoded["iss"], self.account_sid) - self.assertEqual(decoded["account_sid"], self.account_sid) - self.assertEqual(decoded["workspace_sid"], self.workspace_sid) - self.assertEqual(decoded["worker_sid"], self.worker_sid) - self.assertEqual(decoded["channel"], self.worker_sid) - self.assertEqual(decoded["version"], "v1") - self.assertEqual(decoded["friendly_name"], self.worker_sid) + self.check_decoded(decoded, self.account_sid, self.workspace_sid, self.worker_sid, self.worker_sid) def test_generate_token_with_default_ttl(self): token = self.capability.generate_token() @@ -57,56 +74,21 @@ def test_defaults(self): decoded = jwt.decode(token, self.auth_token) self.assertIsNotNone(decoded) - websocket_url = 'https://event-bridge.twilio.com/v1/wschannels/%s/%s' % (self.account_sid, self.worker_sid) + websocket_url = 'https://event-bridge.twilio.com/v1/wschannels/{}/{}'.format(self.account_sid, self.worker_sid) # expect 5 policies policies = decoded['policies'] self.assertEqual(len(policies), 5) - # policy 0 - GET websocket - get_policy = policies[0] - self.assertIsNotNone(get_policy) - self.assertEqual(get_policy['url'], websocket_url) - self.assertEqual(get_policy['method'], 'GET') - self.assertTrue(get_policy['allow']) - self.assertEqual(get_policy['query_filter'], {}) - self.assertEqual(get_policy['post_filter'], {}) - - # policy 1 - POST - post_policy = policies[1] - self.assertIsNotNone(post_policy) - self.assertEqual(post_policy['url'], websocket_url) - self.assertEqual(post_policy['method'], 'POST') - self.assertTrue(post_policy['allow']) - self.assertEqual(post_policy['query_filter'], {}) - self.assertEqual(post_policy['post_filter'], {}) - - # policy 2 - Worker fetch - worker_fetch_policy = policies[2] - self.assertIsNotNone(worker_fetch_policy) - self.assertEqual(worker_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789') - self.assertEqual(worker_fetch_policy['method'], 'GET') - self.assertTrue(worker_fetch_policy['allow']) - self.assertEqual(worker_fetch_policy['query_filter'], {}) - self.assertEqual(worker_fetch_policy['post_filter'], {}) - - # policy 3 - Reservation fetch - reservation_fetch_policy = policies[3] - self.assertIsNotNone(reservation_fetch_policy) - self.assertEqual(reservation_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**') - self.assertEqual(reservation_fetch_policy['method'], 'GET') - self.assertTrue(reservation_fetch_policy['allow']) - self.assertEqual(reservation_fetch_policy['query_filter'], {}) - self.assertEqual(reservation_fetch_policy['post_filter'], {}) - - # policy 4 - Activity fetch - activity_fetch_policy = policies[4] - self.assertIsNotNone(activity_fetch_policy) - self.assertEqual(activity_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities') - self.assertEqual(activity_fetch_policy['method'], 'GET') - self.assertTrue(activity_fetch_policy['allow']) - self.assertEqual(activity_fetch_policy['query_filter'], {}) - self.assertEqual(activity_fetch_policy['post_filter'], {}) + # should expect 5 policies + for method, url, policy in [ + ('GET', websocket_url, policies[0]), + ('POST', websocket_url, policies[1]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", policies[2]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", policies[3]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", policies[4]) + ]: + yield self.check_policy, method, url, policy def test_allow_activity_updates(self): @@ -123,7 +105,7 @@ def test_allow_activity_updates(self): self.assertEqual(len(policies), 6) policy = policies[5] - url = "https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s" % (self.workspace_sid, self.worker_sid) + url = "https://taskrouter.twilio.com/v1/Workspaces/{}/Workers/{}".format(self.workspace_sid, self.worker_sid) self.assertEqual(url, policy["url"]) self.assertEqual("POST", policy["method"]) @@ -147,13 +129,9 @@ def test_allow_reservation_updates(self): policy = policies[5] - url = "https://taskrouter.twilio.com/v1/Workspaces/%s/Tasks/**" % self.workspace_sid + url = "https://taskrouter.twilio.com/v1/Workspaces/{}/Tasks/**".format(self.workspace_sid) - self.assertEqual(url, policy["url"]) - self.assertEqual("POST", policy["method"]) - self.assertTrue(policy["allow"]) - self.assertEqual({}, policy["query_filter"]) - self.assertEqual({}, policy['post_filter']) + self.check_policy('POST', url, policy) if __name__ == "__main__": unittest.main() diff --git a/tests/task_router/test_task_router_workspace_capability.py b/tests/task_router/test_task_router_workspace_capability.py index b35e692897..87287dd103 100644 --- a/tests/task_router/test_task_router_workspace_capability.py +++ b/tests/task_router/test_task_router_workspace_capability.py @@ -1,3 +1,7 @@ +import sys +sys.path.append('../../') +sys.path.append('/Library/Python/2.7/site-packages') + import time import unittest @@ -6,6 +10,25 @@ class TaskRouterWorkspaceCapabilityTest(unittest.TestCase): + def check_policy(self, method, url, policy): + self.assertEqual(url, policy['url']) + self.assertEqual(method, policy['method']) + self.assertTrue(policy['allow']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) + + def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): + self.assertEqual(decoded["iss"], account_sid) + self.assertEqual(decoded["account_sid"], account_sid) + self.assertEqual(decoded["workspace_sid"], workspace_sid) + self.assertEqual(decoded["channel"], channel_id) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], channel_id) + + if 'worker_sid' in decoded.keys(): + self.assertEqual(decoded['worker_sid'], channel_sid) + if 'taskqueue_sid' in decoded.keys(): + self.assertEqual(decoded['taskqueue_sid'], channel_sid) def setUp(self): self.account_sid = "AC123" @@ -21,12 +44,7 @@ def test_generate_token(self): decoded = jwt.decode(token, self.auth_token) self.assertIsNotNone(decoded) - self.assertEqual(decoded["iss"], self.account_sid) - self.assertEqual(decoded["account_sid"], self.account_sid) - self.assertEqual(decoded["workspace_sid"], self.workspace_sid) - self.assertEqual(decoded["channel"], self.workspace_sid) - self.assertEqual(decoded["version"], "v1") - self.assertEqual(decoded["friendly_name"], self.workspace_sid) + self.check_decoded(decoded, self.account_sid, self.workspace_sid, self.workspace_sid) def test_generate_token_with_default_ttl(self): token = self.capability.generate_token() @@ -58,29 +76,12 @@ def test_default(self): policies = decoded['policies'] self.assertEqual(len(policies), 3) - # websocket GET - get_policy = policies[0] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", get_policy['url']) - self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allow']) - self.assertEqual({}, get_policy['query_filter']) - self.assertEqual({}, get_policy['post_filter']) - - # websocket POST - post_policy = policies[1] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", post_policy['url']) - self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allow']) - self.assertEqual({}, post_policy['query_filter']) - self.assertEqual({}, post_policy['post_filter']) - - # fetch GET - fetch_policy = policies[2] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456", fetch_policy['url']) - self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allow']) - self.assertEqual({}, fetch_policy['query_filter']) - self.assertEqual({}, fetch_policy['post_filter']) + for method, url, policy in [ + ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", policies[0]), + ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", policies[1]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456", policies[2]) + ]: + yield self.check_policy, method, url, policy def test_allow_fetch_subresources(self): self.capability.allow_fetch_subresources() @@ -95,14 +96,9 @@ def test_allow_fetch_subresources(self): self.assertEqual(len(policies), 4) # confirm the additional policy generated with allow_fetch_subresources() - policy = policies[3] - self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/**") - self.assertEqual(policy['method'], "GET") - self.assertTrue(policy['allow']) - self.assertEqual({}, policy['query_filter']) - self.assertEqual({}, policy['post_filter']) + self.check_policy('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/**", policy) def test_allow_updates_subresources(self): self.capability.allow_updates_subresources() @@ -117,14 +113,9 @@ def test_allow_updates_subresources(self): self.assertEqual(len(policies), 4) # confirm the additional policy generated with allow_updates_subresources() - policy = policies[3] - self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/**") - self.assertEqual(policy['method'], "POST") - self.assertTrue(policy['allow']) - self.assertEqual({}, policy['query_filter']) - self.assertEqual({}, policy['post_filter']) + self.check_policy('POST', "https://taskrouter.twilio.com/v1/Workspaces/WS456/**", policy) if __name__ == "__main__": unittest.main() diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index 58a313d5e2..065816c124 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -11,7 +11,6 @@ REQUIRED = {'required': True} OPTIONAL = {'required': False} - def deprecated(func): def log_warning(*args, **kwargs): # stacklevel = 2 makes the warning refer to the caller of the @@ -32,9 +31,7 @@ def __init__(self, account_sid, auth_token, workspace_sid, channel_id): self.workspace_sid = workspace_sid self.channel_id = channel_id - self.base_url = (TASK_ROUTER_BASE_URL + "/" + - TASK_ROUTER_VERSION + - "/Workspaces/" + workspace_sid) + self.base_url = "{}/{}/Workspaces/{}".format(TASK_ROUTER_BASE_URL, TASK_ROUTER_VERSION, workspace_sid) # validate the JWT self.validate_jwt() @@ -48,10 +45,14 @@ def __init__(self, account_sid, auth_token, workspace_sid, channel_id): # add permissions to fetch the instance resource self.add_policy(self.resource_url, "GET", True) + @property + def channel_prefix(self): + return self.channel_id[0:2] + def setup_resource(self): - if self.channel_id[0:2] == "WS": + if self.channel_prefix == "WS": self.resource_url = self.base_url - elif self.channel_id[0:2] == "WK": + elif self.channel_prefix == "WK": self.resource_url = self.base_url + "/Workers/" + self.channel_id activity_url = self.base_url + "/Activities" @@ -60,7 +61,7 @@ def setup_resource(self): reservations_url = self.base_url + "/Tasks/**" self.allow(reservations_url, "GET") - elif self.channel_id[0:2] == "WQ": + elif self.channel_prefix == "WQ": self.resource_url = self.base_url + \ "/TaskQueues/" + self.channel_id @@ -81,8 +82,7 @@ def validate_jwt(self): if self.channel_id is None: raise ValueError('ChannelId not provided') - prefix = self.channel_id[0:2] - if prefix != "WS" and prefix != "WK" and prefix != "WQ": + if self.channel_prefix != "WS" and self.channel_prefix != "WK" and self.channel_prefix != "WQ": raise ValueError('Invalid ChannelId provided: ' + self.channel_id) def allow_fetch_subresources(self): @@ -102,16 +102,16 @@ def allow_delete_subresources(self): @deprecated def allow_worker_fetch_attributes(self): - if self.channel_id[0:2] == "WK": + if self.channel_prefix != "WK": + raise ValueError("Deprecated func not applicable to non Worker") + else: self.policies.append(self.make_policy( self.resource_url, 'GET')) - else: - raise ValueError("Deprecated func not applicable to non Worker") @deprecated def allow_worker_activity_updates(self): - if self.channel_id[0:2] == "WK": + if self.channel_prefix == "WK": self.policies.append(self.make_policy( self.resource_url, 'POST', @@ -122,7 +122,7 @@ def allow_worker_activity_updates(self): @deprecated def allow_task_reservation_updates(self): - if self.channel_id[0:2] == "WK": + if self.channel_prefix == "WK": tasks_url = self.base_url + "/Tasks/**" self.policies.append(self.make_policy( tasks_url, @@ -147,14 +147,15 @@ def deny(self, url, method, query_filter=None, post_filter=None): def make_policy(self, url, method, allowed=True, query_filter=None, post_filter=None): - # Create a policy dictionary for the given resource and method. - # :param str url: the resource URL to grant or deny access to - # :param str method: the HTTP method to allow or deny - # :param allowed bool: whether this request is allowed - # :param dict query_filter: specific GET parameter names - # to require or allow - # :param dict post_filter: POST parameter names - # to require or allow + """Create a policy dictionary for the given resource and method. + :param str url: the resource URL to grant or deny access to + :param str method: the HTTP method to allow or deny + :param allowed bool: whether this request is allowed + :param dict query_filter: specific GET parameter names + to require or allow + :param dict post_filter: POST parameter names + to require or allow + """ return { 'url': url, @@ -168,14 +169,15 @@ def get_resource_url(self): return self.resource_url def generate_token(self, ttl=3600): - task_router_attributes = {} - task_router_attributes["account_sid"] = self.account_sid - task_router_attributes["workspace_sid"] = self.workspace_sid - task_router_attributes["channel"] = self.channel_id + task_router_attributes = { + 'account_sid': self.account_sid, + 'workspace_sid': self.workspace_sid, + 'channel': self.channel_id + } - if self.channel_id[0:2] == "WK": + if self.channel_prefix == "WK": task_router_attributes["worker_sid"] = self.channel_id - elif self.channel_id[0:2] == "WQ": + elif self.channel_prefix == "WQ": task_router_attributes["taskqueue_sid"] = self.channel_id return self._generate_token(ttl, task_router_attributes) From 0255c472182e7c01d732e6e24c14c2f80f6d4682 Mon Sep 17 00:00:00 2001 From: Jen Li Date: Tue, 28 Jul 2015 16:00:24 -0700 Subject: [PATCH 10/42] forgot to remove sys path --- tests/task_router/test_task_router_capability.py | 4 ---- tests/task_router/test_task_router_worker_capability.py | 4 ---- tests/task_router/test_task_router_workspace_capability.py | 4 ---- 3 files changed, 12 deletions(-) diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py index 7d1a99c644..3f3f4f0fb5 100644 --- a/tests/task_router/test_task_router_capability.py +++ b/tests/task_router/test_task_router_capability.py @@ -1,7 +1,3 @@ -import sys -sys.path.append('../../') -sys.path.append('/Library/Python/2.7/site-packages') - import unittest import warnings diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py index e2ca7a2cd9..6fad95e558 100644 --- a/tests/task_router/test_task_router_worker_capability.py +++ b/tests/task_router/test_task_router_worker_capability.py @@ -1,7 +1,3 @@ -import sys -sys.path.append('../../') -sys.path.append('/Library/Python/2.7/site-packages') - import time import unittest diff --git a/tests/task_router/test_task_router_workspace_capability.py b/tests/task_router/test_task_router_workspace_capability.py index 87287dd103..3f4f644389 100644 --- a/tests/task_router/test_task_router_workspace_capability.py +++ b/tests/task_router/test_task_router_workspace_capability.py @@ -1,7 +1,3 @@ -import sys -sys.path.append('../../') -sys.path.append('/Library/Python/2.7/site-packages') - import time import unittest From c58cb4ae201f2a0c6b14928b4aa0422a16f42514 Mon Sep 17 00:00:00 2001 From: Jen Li Date: Tue, 28 Jul 2015 16:02:25 -0700 Subject: [PATCH 11/42] removed trailing whitespaces --- tests/task_router/test_task_router_capability.py | 4 ++-- tests/task_router/test_task_router_worker_capability.py | 4 ++-- tests/task_router/test_task_router_workspace_capability.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py index 3f3f4f0fb5..0fa6c45805 100644 --- a/tests/task_router/test_task_router_capability.py +++ b/tests/task_router/test_task_router_capability.py @@ -14,7 +14,7 @@ def check_policy(self, method, url, policy): self.assertEqual({}, policy['query_filter']) self.assertEqual({}, policy['post_filter']) - def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): + def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): self.assertEqual(decoded["iss"], account_sid) self.assertEqual(decoded["account_sid"], account_sid) self.assertEqual(decoded["workspace_sid"], workspace_sid) @@ -22,7 +22,7 @@ def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel self.assertEqual(decoded["version"], "v1") self.assertEqual(decoded["friendly_name"], channel_id) - if 'worker_sid' in decoded.keys(): + if 'worker_sid' in decoded.keys(): self.assertEqual(decoded['worker_sid'], channel_sid) if 'taskqueue_sid' in decoded.keys(): self.assertEqual(decoded['taskqueue_sid'], channel_sid) diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py index 6fad95e558..4f7578d91b 100644 --- a/tests/task_router/test_task_router_worker_capability.py +++ b/tests/task_router/test_task_router_worker_capability.py @@ -13,7 +13,7 @@ def check_policy(self, method, url, policy): self.assertEqual({}, policy['query_filter']) self.assertEqual({}, policy['post_filter']) - def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): + def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): self.assertEqual(decoded["iss"], account_sid) self.assertEqual(decoded["account_sid"], account_sid) self.assertEqual(decoded["workspace_sid"], workspace_sid) @@ -21,7 +21,7 @@ def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel self.assertEqual(decoded["version"], "v1") self.assertEqual(decoded["friendly_name"], channel_id) - if 'worker_sid' in decoded.keys(): + if 'worker_sid' in decoded.keys(): self.assertEqual(decoded['worker_sid'], channel_sid) if 'taskqueue_sid' in decoded.keys(): self.assertEqual(decoded['taskqueue_sid'], channel_sid) diff --git a/tests/task_router/test_task_router_workspace_capability.py b/tests/task_router/test_task_router_workspace_capability.py index 3f4f644389..2767afd59d 100644 --- a/tests/task_router/test_task_router_workspace_capability.py +++ b/tests/task_router/test_task_router_workspace_capability.py @@ -13,7 +13,7 @@ def check_policy(self, method, url, policy): self.assertEqual({}, policy['query_filter']) self.assertEqual({}, policy['post_filter']) - def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): + def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): self.assertEqual(decoded["iss"], account_sid) self.assertEqual(decoded["account_sid"], account_sid) self.assertEqual(decoded["workspace_sid"], workspace_sid) @@ -21,7 +21,7 @@ def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel self.assertEqual(decoded["version"], "v1") self.assertEqual(decoded["friendly_name"], channel_id) - if 'worker_sid' in decoded.keys(): + if 'worker_sid' in decoded.keys(): self.assertEqual(decoded['worker_sid'], channel_sid) if 'taskqueue_sid' in decoded.keys(): self.assertEqual(decoded['taskqueue_sid'], channel_sid) From ef8d9b9621262cb9212d10c6dfa92ba7d55746fc Mon Sep 17 00:00:00 2001 From: Jen Li Date: Tue, 28 Jul 2015 16:23:37 -0700 Subject: [PATCH 12/42] removed unnecessary constructors and added .format() --- twilio/task_router/__init__.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index 065816c124..4224884117 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -62,12 +62,10 @@ def setup_resource(self): self.allow(reservations_url, "GET") elif self.channel_prefix == "WQ": - self.resource_url = self.base_url + \ - "/TaskQueues/" + self.channel_id + self.resource_url = "{}/TaskQueues/{}".format(self.base_url, self.channel_id) def allow_web_sockets(self, channel_id): - web_socket_url = TASK_ROUTER_BASE_EVENTS_URL + "/" + \ - self.account_sid + "/" + self.channel_id + web_socket_url = "{}/{}/{}".format(TASK_ROUTER_BASE_EVENTS_URL, self.account_sid, self.channel_id) self.policies.append(self.make_policy(web_socket_url, "GET", True)) self.policies.append(self.make_policy(web_socket_url, "POST", True)) @@ -230,22 +228,10 @@ def allow_reservation_updates(self): class TaskRouterTaskQueueCapability(TaskRouterCapability): - def __init__(self, account_sid, auth_token, workspace_sid, taskqueue_sid): - super(TaskRouterTaskQueueCapability, self).__init__(account_sid, - auth_token, - workspace_sid, - taskqueue_sid) - def setup_resource(self): self.resource_url = self.base_url + "/TaskQueues/" + self.channel_id class TaskRouterWorkspaceCapability(TaskRouterCapability): - def __init__(self, account_sid, auth_token, workspace_sid): - super(TaskRouterWorkspaceCapability, self).__init__(account_sid, - auth_token, - workspace_sid, - workspace_sid) - def setup_resource(self): self.resource_url = self.base_url From 7435b95610eda19b0205c01ca1f1830611f3c268 Mon Sep 17 00:00:00 2001 From: Jen Li Date: Wed, 29 Jul 2015 17:53:02 -0700 Subject: [PATCH 13/42] added back constructor for TaskRouterWorkspaceCapability and fixed 80 chars --- .../test_task_router_capability.py | 6 +++--- twilio/task_router/__init__.py | 20 +++++++++++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py index 0fa6c45805..a32768bfbf 100644 --- a/tests/task_router/test_task_router_capability.py +++ b/tests/task_router/test_task_router_capability.py @@ -69,7 +69,7 @@ def test_worker_default(self): decoded = jwt.decode(token, auth_token) self.assertIsNotNone(decoded) - self.check_decoded(decoded, account_sid, workspace_sid, channel_id, worker_sid) + self.check_decoded(decoded, account_sid, workspace_sid, worker_sid, worker_sid) policies = decoded['policies'] self.assertEqual(len(policies), 5) @@ -98,7 +98,7 @@ def test_task_queue_default(self): decoded = jwt.decode(token, auth_token) self.assertIsNotNone(decoded) - self.check_decoded(decoded, account_sid, workspace_sid, channel_id, taskqueue_sid) + self.check_decoded(decoded, account_sid, workspace_sid, taskqueue_sid, taskqueue_sid) policies = decoded['policies'] self.assertEqual(len(policies), 3) @@ -125,7 +125,7 @@ def test_deprecated_worker(self): decoded = jwt.decode(token, auth_token) self.assertIsNotNone(decoded) - self.check_decoded(decoded, account_sid, workspace_sid, channel_id, worker_sid) + self.check_decoded(decoded, account_sid, workspace_sid, worker_sid, worker_sid) policies = decoded['policies'] self.assertEqual(len(policies), 5) diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index 4224884117..91dca999a6 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -11,6 +11,7 @@ REQUIRED = {'required': True} OPTIONAL = {'required': False} + def deprecated(func): def log_warning(*args, **kwargs): # stacklevel = 2 makes the warning refer to the caller of the @@ -31,7 +32,9 @@ def __init__(self, account_sid, auth_token, workspace_sid, channel_id): self.workspace_sid = workspace_sid self.channel_id = channel_id - self.base_url = "{}/{}/Workspaces/{}".format(TASK_ROUTER_BASE_URL, TASK_ROUTER_VERSION, workspace_sid) + self.base_url = "{}/{}/Workspaces/{}".format(TASK_ROUTER_BASE_URL, + TASK_ROUTER_VERSION, + workspace_sid) # validate the JWT self.validate_jwt() @@ -62,10 +65,12 @@ def setup_resource(self): self.allow(reservations_url, "GET") elif self.channel_prefix == "WQ": - self.resource_url = "{}/TaskQueues/{}".format(self.base_url, self.channel_id) + self.resource_url = "{}/TaskQueues/{}".format( + self.base_url, self.channel_id) def allow_web_sockets(self, channel_id): - web_socket_url = "{}/{}/{}".format(TASK_ROUTER_BASE_EVENTS_URL, self.account_sid, self.channel_id) + web_socket_url = "{}/{}/{}".format(TASK_ROUTER_BASE_EVENTS_URL, + self.account_sid, self.channel_id) self.policies.append(self.make_policy(web_socket_url, "GET", True)) self.policies.append(self.make_policy(web_socket_url, "POST", True)) @@ -80,7 +85,8 @@ def validate_jwt(self): if self.channel_id is None: raise ValueError('ChannelId not provided') - if self.channel_prefix != "WS" and self.channel_prefix != "WK" and self.channel_prefix != "WQ": + if self.channel_prefix != "WS" and self.channel_prefix != "WK" \ + and self.channel_prefix != "WQ": raise ValueError('Invalid ChannelId provided: ' + self.channel_id) def allow_fetch_subresources(self): @@ -233,5 +239,11 @@ def setup_resource(self): class TaskRouterWorkspaceCapability(TaskRouterCapability): + def __init__(self, account_sid, auth_token, workspace_sid): + super(TaskRouterWorkspaceCapability, self).__init__(account_sid, + auth_token, + workspace_sid, + workspace_sid) + def setup_resource(self): self.resource_url = self.base_url From 05d350e468d441ef86176f1c0e6c08734e70b0a0 Mon Sep 17 00:00:00 2001 From: gramanathaiah Date: Tue, 4 Aug 2015 15:11:37 -0700 Subject: [PATCH 14/42] Incorporated review comments --- docs/usage/taskrouter.rst | 266 +++++++++++++++++- tests/task_router/test_workflow_config.py | 97 ++++--- .../task_router/taskrouter_config.py | 27 +- .../resources/task_router/workflow_config.py | 18 +- .../resources/task_router/workflow_rule.py | 18 +- 5 files changed, 337 insertions(+), 89 deletions(-) diff --git a/docs/usage/taskrouter.rst b/docs/usage/taskrouter.rst index 76c095a9e9..a903aaae36 100644 --- a/docs/usage/taskrouter.rst +++ b/docs/usage/taskrouter.rst @@ -40,6 +40,36 @@ its unique ID. .. +The following code will get an instance of an existing :class:`workspace` resource + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + workspace = client.workspaces.get(WORKSPACE_SID) + print workspace.friendly_name +.. + +The following code will get the list of all existing :class:`workspace` resources + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + for workspace in client.workspaces.list() + print workspace.friendly_name +.. + The following code will create a update an existing :class:`Workspace` resource .. code-block:: python @@ -72,6 +102,8 @@ The following code will delete an existing :class:`workspace` resource client.workspaces.delete(WORKSPACE_SID) .. + + Workflows --------- @@ -130,6 +162,48 @@ unique ID: .. +The following code will get a instance of an existing :class:`workflow` resource + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + workflow = client.workflows(WORKSPACE_SID).get(WORKFLOW_SID) + print workflow.friendly_name + +.. + + + +The following code will get a list of all existing :class:`workflow` resources + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + for workflow in client.workflows(WORKSPACE_SID).list() + print workflow.friendly_name + +.. + The following code will update an existing :class:`workflow` resource .. code-block:: python @@ -241,6 +315,44 @@ To create a new :class:`Activity`: .. +To get an existing :class:`activity` resource + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + activity = client.activities(WORKSPACE_SID).get(ACTIVITY_SID) + print activity.friendly_name + +.. + +To get a list of existing :class:`activity` resources + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + for activity in client.activities(WORKSPACE_SID).list() + print activity.friendly_name + +.. + To update an existing :class:`Activity` .. code-block:: python @@ -315,6 +427,44 @@ To create a new :class:`Worker`: .. +To get an existing :class:`worker` instance + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + worker = client.workers(WORKSPACE_SID).get(WORKER_SID) + print worker_friendly_name; +.. + + +To get an existing :class:`worker` list + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + for worker in client.workers(WORKSPACE_SID).list() + print worker_friendly_name; +.. + + To update an existing :class:`Worker` .. code-block:: python @@ -342,7 +492,7 @@ To update an existing :class:`Worker` .. -To delete an exisitng :class:`Worker` +To delete an existing :class:`Worker` .. code-block:: python @@ -396,6 +546,49 @@ To create a new :class:`TaskQueue`: .. +To get an existing :class`TaskQueue` instance + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + queue = client.task_queues(WORKSPACE_SID).get(TASKQUEUE_SID) + print queue.sid + +.. + + + +To get an existing :class`TaskQueue` list + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + for queue in client.task_queues(WORKSPACE_SID).list() + print queue.sid + +.. + + To update an existing :class:`TaskQueue` .. code-block:: python @@ -484,7 +677,64 @@ To create a new :class:`Task` via the REST API: print task.sid .. -To update an exisiting :class:`Task` +To get an existing :class:`Task` instance + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + WORKFLOW_SID = "WWXXXXXXXXXXXXXX" + # Some JSON containing attributes for this task. User-defined. + TASK_ATTRIBUTES = """{ + "type": "call", + "contact": "+2014068777", + "customer-value": "gold", + "task-reason": "support", + "callSid": "CA42ed11..." + }""" + + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + task = client.tasks(WORKSPACE_SID).delete(TASK_SID) + print task.attributes +.. + + +To get an existing :class:`Task` list + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + WORKFLOW_SID = "WWXXXXXXXXXXXXXX" + # Some JSON containing attributes for this task. User-defined. + TASK_ATTRIBUTES = """{ + "type": "call", + "contact": "+2014068777", + "customer-value": "gold", + "task-reason": "support", + "callSid": "CA42ed11..." + }""" + + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + for task in client.tasks(WORKSPACE_SID).list() + print task.attributes +.. + +To update an existing :class:`Task` .. code-block:: python @@ -517,7 +767,7 @@ To update an exisiting :class:`Task` print task.sid .. -To delete an exisitng :class:`Task` +To delete an existing :class:`Task` .. code-block:: python @@ -574,13 +824,13 @@ Using Workflow builder helper classes to create a :class:`Workflow` resource. rules.append(anotherRule); defaultTarget = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916","1==1",None,None) config = WorkflowConfig(rules,defaultTarget) - print config.toJson() + print config.to_json() workflow = client.workflows(WORKSPACE_SID).create( - friendly_name="Incoming Call Flow", - assignment_callback_url="https://example.com/callback", - fallback_assignment_callback_url="https://example.com/callback2", - configuration=config.toJson() + friendly_name= "Incoming Call Flow", + assignment_callback_url= "https://example.com/callback", + fallback_assignment_callback_url= "https://example.com/callback2", + configuration= config.to_json() ) print workflow.sid diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py index cb2e772e03..d191560e26 100644 --- a/tests/task_router/test_workflow_config.py +++ b/tests/task_router/test_workflow_config.py @@ -1,59 +1,56 @@ import unittest import json -from mock import patch, Mock - - - -from tests.tools import create_mock_json from twilio.rest.resources.task_router.workflow_config import WorkflowConfig from twilio.rest.resources.task_router.workflow_rule import WorkflowRule from twilio.rest.resources.task_router.workflow_ruletarget import WorkflowRuleTarget -class WorkflowConfigTest(unittest.TestCase): - def test_to_json(self): - rules =[] - ruleTargets=[] - ruleTargets1=[] - ruleTarget = WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7","1==1",1,20) - ruleTarget1 = WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da","1==1",1,210) - ruleTargets.append(ruleTarget); - ruleTargets1.append(ruleTarget1); - rule = WorkflowRule("1==1",ruleTargets,"SomeQ") - rules.append(rule) - rule1 = WorkflowRule("1==1",ruleTargets1,"SomeOtherQ") - rules.append(rule1) - deftarget = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916","1==1",None,None) - - - config = WorkflowConfig(rules,deftarget) - self.assertEqual(self.is_json(config.toJson()),True) - - - - - def test_from_Json(self): - - data="{\"task_routing\": { \"filters\": [ { \"targets\": [ { \"queue\": \"WQec62de0e1148b8477f2e24579779c8b1\", \"expression\": \"task.language IN worker.languages\" } ], \"friendly_name\": \"Sales\", \"expression\": \"type == \\\"sales\\\"\" }, { \"targets\": [ { \"queue\": \"WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f\", \"expression\": \"task.language IN worker.languages\" } ], \"friendly_name\": \"Marketing\", \"expression\": \"type == \\\"marketing\\\"\" }, { \"targets\": [ { \"queue\": \"WQe5eb317eb23500ade45087ea6522896c\", \"expression\": \"task.language IN worker.languages\" } ], \"friendly_name\": \"Support\", \"expression\": \"type == \\\"support\\\"\" } ], \"default_filter\": { \"queue\": \"WQ05f810d2d130344fd56e3c91ece2e594\" } }}" - config = WorkflowConfig.json2obj(data) - self.assertEqual(len(config.task_routing.filters),3) - self.assertEqual(len(config.task_routing.default_filter),1) - - - - def test_from_json2(self): - data ="{ \"task_routing\": { \"default_filter\": { \"expression\": null, \"priority\": null, \"queue\": \"WQYYYYY\", \"timeout\": null }, \"filters\": [ { \"expression\": \"1==1\", \"friendly_name\": \"SomeQ\", \"targets\": [ { \"expression\": \"1==1\", \"priority\": 1, \"queue\": \"WQXXXX\", \"timeout\": 20 } ] }, { \"expression\": \"1==1\", \"friendly_name\": \"SomeOtherQ\", \"targets\": [ { \"expression\": \"1==1\", \"priority\": 1, \"queue\": \"WQXXXX\", \"timeout\": 20 } ] } ] }}" - config = WorkflowConfig.json2obj(data) - self.assertEqual(len(config.task_routing.filters),2) - self.assertEqual(len(config.task_routing.default_filter),4) - - - - def is_json(self,myjson): - try: - json_object = json.loads(myjson) - except ValueError, e: - return False - return True +class WorkflowConfigTest(unittest.TestCase): + def test_to_json(self): + rules = [] + rule_targets= [] + rule_targets1= [] + rule_target = WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20) + rule_target1 = WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210) + rule_targets.append(rule_target); + rule_targets1.append(rule_target1); + rule = WorkflowRule("1==1", rule_targets, "SomeQ") + rules.append(rule) + rule1 = WorkflowRule("1==1", rule_targets1, "SomeOtherQ") + rules.append(rule1) + def_target = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916", "1==1", None, None) + config = WorkflowConfig(rules , def_target) + self.assertEqual(self.is_json(config.to_json()), True) + + def test_from_json(self): + + data = "{\"task_routing\": {\"filters\": [{\"targets\": [{\"queue\": \"WQec62de0e1148b8477f2e24579779c8b1\"," \ + "\"expression\": \"task.language IN worker.languages\"}],\"friendly_name\": \"Sales\",\"expression\": " \ + "\"type == \\\"sales\\\"\"},{\"targets\": [{\"queue\": \"WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f\"," \ + "\"expression\": \"task.language IN worker.languages\"}],\"friendly_name\": \"Marketing\",\"expression\":" \ + " \"type == \\\"marketing\\\"\"},{\"targets\": [{\"queue\": \"WQe5eb317eb23500ade45087ea6522896c\"," \ + "\"expression\": \"task.language IN worker.languages\"}],\"friendly_name\": \"Support\"," \ + "\"expression\": \"type == \\\"support\\\"\"}],\"default_filter\": " \ + "{\"queue\": \"WQ05f810d2d130344fd56e3c91ece2e594\"}}}" + config = WorkflowConfig.json2obj(data) + self.assertEqual(len(config.task_routing.filters), 3) + self.assertEqual(len(config.task_routing.default_filter), 1) + + def test_from_json2(self): + data = "{\"task_routing\": {\"default_filter\": {\"expression\": null,\"priority\": null,\"queue\": \"WQYYYYY\"," \ + "\"timeout\": null },\"filters\": [{\"expression\": \"1==1\",\"friendly_name\": \"SomeQ\",\"targets\": [" \ + "{\"expression\": \"1==1\",\"priority\": 1,\"queue\": \"WQXXXX\",\"timeout\": 20}]},{\"expression\": \"1==1\"," \ + "\"friendly_name\": \"SomeOtherQ\",\"targets\": [{\"expression\": \"1==1\",\"priority\": 1,\"queue\": \"WQXXXX\"," \ + "\"timeout\": 20}]}]}}" + config = WorkflowConfig.json2obj(data) + self.assertEqual(len(config.task_routing.filters), 2) + self.assertEqual(len(config.task_routing.default_filter), 4) + + def is_json(self, myjson): + try: + json.loads(myjson) + except ValueError, e: + return False + return True diff --git a/twilio/rest/resources/task_router/taskrouter_config.py b/twilio/rest/resources/task_router/taskrouter_config.py index 67c12813de..36593cfc6e 100644 --- a/twilio/rest/resources/task_router/taskrouter_config.py +++ b/twilio/rest/resources/task_router/taskrouter_config.py @@ -1,24 +1,25 @@ from .workflow_rule import WorkflowRule from .workflow_ruletarget import WorkflowRuleTarget + + class TaskRouterConfig: - """ - TaskRouterConfig represents the filter and default_filter - of a workflow configuration of taskrouter - """ + """ + TaskRouterConfig represents the filter and default_filter + of a workflow configuration of taskrouter + """ - def __init__(self, rules, defaultTarget): + def __init__(self, rules, default_target): self.filters = rules - self.default_filter = defaultTarget + self.default_filter = default_target - @property - def filters(self): + @property + def filters(self): return self.filters - @property - def defaultFilter(self): + @property + def default_filter(self): return self.default_filter - def __repr__(self): - out = self.__dict__ - return out + def __repr__(self): + return self.__dict__ diff --git a/twilio/rest/resources/task_router/workflow_config.py b/twilio/rest/resources/task_router/workflow_config.py index 144f3e36ab..b61930ad48 100644 --- a/twilio/rest/resources/task_router/workflow_config.py +++ b/twilio/rest/resources/task_router/workflow_config.py @@ -12,21 +12,19 @@ class WorkflowConfig: filters and default_filter. """ - def __init__(self, workflowRules, defaultTarget): - #filters and default_filters - self.task_routing = TaskRouterConfig(workflowRules, defaultTarget) + def __init__(self, workflow_rules, default_target): + # filters and default_filters + self.task_routing = TaskRouterConfig(workflow_rules, default_target) @property - def taskrouterConfig(self): + def taskrouter_config(self): return self.task_routing - def toJson(self): - return json.dumps(self, default=lambda o: o.__dict__, - sort_keys=True, indent=4) - + def to_json(self): + return json.dumps(self, default=lambda o: o.__dict__,sort_keys=True, indent=4) @staticmethod def json2obj(data): - m=json.loads(data) - return WorkflowConfig(m['task_routing']['filters'],m['task_routing']['default_filter']) + m = json.loads(data) + return WorkflowConfig(m['task_routing']['filters'], m['task_routing']['default_filter']) diff --git a/twilio/rest/resources/task_router/workflow_rule.py b/twilio/rest/resources/task_router/workflow_rule.py index fe16c93029..aa4650ef29 100644 --- a/twilio/rest/resources/task_router/workflow_rule.py +++ b/twilio/rest/resources/task_router/workflow_rule.py @@ -17,11 +17,12 @@ class WorkflowRule: The name of the filter """ _targets = list() - def __init__(self, expression, targets, friendlyName): + + def __init__(self, expression, targets, friendly_name): self.expression = expression self.targets = targets - self.friendly_name = friendlyName + self.friendly_name = friendly_name @property def expression(self): @@ -32,12 +33,13 @@ def targets(self): return self.targets @property - def friendlyName(self): + def friendly_name(self): return self.friendly_name def __repr__(self): - out = dict() - out['expression'] = self.expression - out['friendlyName'] = self.friendly_name - out['target'] = self.targets - return str(out) + return str({ + 'expression': self.expression, + 'friendly_name': self.friendly_name, + 'target': self.target, + }) + From abdb4105a6f442aa9a869e34481e26e346693116 Mon Sep 17 00:00:00 2001 From: gramanathaiah Date: Tue, 4 Aug 2015 15:17:07 -0700 Subject: [PATCH 15/42] Removing unwanted properties --- .../resources/task_router/workflow_ruletarget.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/twilio/rest/resources/task_router/workflow_ruletarget.py b/twilio/rest/resources/task_router/workflow_ruletarget.py index dd1da9d13a..987ac0f911 100644 --- a/twilio/rest/resources/task_router/workflow_ruletarget.py +++ b/twilio/rest/resources/task_router/workflow_ruletarget.py @@ -26,18 +26,3 @@ def __init__(self, queue, expression, priority, timeout): self.priority = priority self.timeout = timeout - @property - def queue(self): - return self.queue - - @property - def expression(self): - return self.expression - - @property - def priority(self): - return self.priority - - @property - def timeout(self): - return self.timeout From e0f4295e440066415dde31a5d4b53e6502de4105 Mon Sep 17 00:00:00 2001 From: gramanathaiah Date: Tue, 4 Aug 2015 15:19:35 -0700 Subject: [PATCH 16/42] Formatting --- twilio/rest/resources/task_router/workflow_config.py | 4 ---- twilio/rest/resources/task_router/workflow_rule.py | 3 +++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/twilio/rest/resources/task_router/workflow_config.py b/twilio/rest/resources/task_router/workflow_config.py index b61930ad48..a51e045c6d 100644 --- a/twilio/rest/resources/task_router/workflow_config.py +++ b/twilio/rest/resources/task_router/workflow_config.py @@ -1,8 +1,5 @@ from .taskrouter_config import TaskRouterConfig import json -from collections import namedtuple, Iterable, OrderedDict -import numpy as np -from twilio.rest.resources.task_router.workflow_rule import WorkflowRule class WorkflowConfig: @@ -16,7 +13,6 @@ def __init__(self, workflow_rules, default_target): # filters and default_filters self.task_routing = TaskRouterConfig(workflow_rules, default_target) - @property def taskrouter_config(self): return self.task_routing diff --git a/twilio/rest/resources/task_router/workflow_rule.py b/twilio/rest/resources/task_router/workflow_rule.py index aa4650ef29..2df6fb77c9 100644 --- a/twilio/rest/resources/task_router/workflow_rule.py +++ b/twilio/rest/resources/task_router/workflow_rule.py @@ -1,5 +1,8 @@ from .workflow_ruletarget import WorkflowRuleTarget + + class WorkflowRule: + """ WorkflowRule represents the top level filter which contains a 1 or more targets From 3dea61ab97d6fed2b333f4e922c3e3c4c85b6998 Mon Sep 17 00:00:00 2001 From: gramanathaiah Date: Tue, 4 Aug 2015 16:28:34 -0700 Subject: [PATCH 17/42] Formatting and removing properties on all classes. --- docs/usage/taskrouter.rst | 22 +++++++++---------- .../task_router/taskrouter_config.py | 8 ------- .../resources/task_router/workflow_config.py | 6 +---- .../resources/task_router/workflow_rule.py | 13 ----------- 4 files changed, 12 insertions(+), 37 deletions(-) diff --git a/docs/usage/taskrouter.rst b/docs/usage/taskrouter.rst index a903aaae36..05566a7719 100644 --- a/docs/usage/taskrouter.rst +++ b/docs/usage/taskrouter.rst @@ -812,18 +812,18 @@ Using Workflow builder helper classes to create a :class:`Workflow` resource. WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" rules =[] - ruleTargets=[] - anotherRuleTargets=[] - ruleTarget = WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7","1==1",1,20) - anotherRuleTarget= WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da","1==1",1,210) - ruleTargets.append(ruleTarget); - anotherRuleTargets.append(anotherRuleTarget); - rule = WorkflowRule("1==1",ruleTargets,"SomeQ") + rule_targets=[] + another_rule_targets=[] + rule_target = WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20) + another_rule_target= WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210) + rule_targets.append(rule_target); + another_rule_targets.append(another_rule_target); + rule = WorkflowRule("1==1", rule_targets, "SomeQ") rules.append(rule) - anotherRule = WorkflowRule("1==1",ruleTargets1,"SomeOtherQ") - rules.append(anotherRule); - defaultTarget = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916","1==1",None,None) - config = WorkflowConfig(rules,defaultTarget) + another_rule = WorkflowRule("1==1", rule_targets1, "SomeOtherQ") + rules.append(another_rule); + default_target = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916", "1==1", None, None) + config = WorkflowConfig(rules, default_target) print config.to_json() workflow = client.workflows(WORKSPACE_SID).create( diff --git a/twilio/rest/resources/task_router/taskrouter_config.py b/twilio/rest/resources/task_router/taskrouter_config.py index 36593cfc6e..b4e8eb7b55 100644 --- a/twilio/rest/resources/task_router/taskrouter_config.py +++ b/twilio/rest/resources/task_router/taskrouter_config.py @@ -13,13 +13,5 @@ def __init__(self, rules, default_target): self.filters = rules self.default_filter = default_target - @property - def filters(self): - return self.filters - - @property - def default_filter(self): - return self.default_filter - def __repr__(self): return self.__dict__ diff --git a/twilio/rest/resources/task_router/workflow_config.py b/twilio/rest/resources/task_router/workflow_config.py index a51e045c6d..e79881b3f7 100644 --- a/twilio/rest/resources/task_router/workflow_config.py +++ b/twilio/rest/resources/task_router/workflow_config.py @@ -13,12 +13,8 @@ def __init__(self, workflow_rules, default_target): # filters and default_filters self.task_routing = TaskRouterConfig(workflow_rules, default_target) - @property - def taskrouter_config(self): - return self.task_routing - def to_json(self): - return json.dumps(self, default=lambda o: o.__dict__,sort_keys=True, indent=4) + return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4) @staticmethod def json2obj(data): diff --git a/twilio/rest/resources/task_router/workflow_rule.py b/twilio/rest/resources/task_router/workflow_rule.py index 2df6fb77c9..92a74b5bb9 100644 --- a/twilio/rest/resources/task_router/workflow_rule.py +++ b/twilio/rest/resources/task_router/workflow_rule.py @@ -19,7 +19,6 @@ class WorkflowRule: The name of the filter """ - _targets = list() def __init__(self, expression, targets, friendly_name): @@ -27,18 +26,6 @@ def __init__(self, expression, targets, friendly_name): self.targets = targets self.friendly_name = friendly_name - @property - def expression(self): - return self.expression - - @property - def targets(self): - return self.targets - - @property - def friendly_name(self): - return self.friendly_name - def __repr__(self): return str({ 'expression': self.expression, From 92a549b47322fc6e982d4efc8bb192bc9a92e35e Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Wed, 5 Aug 2015 13:37:50 -0700 Subject: [PATCH 18/42] Add missing Enqueue->Task Twiml generation --- tests/test_twiml.py | 67 +++++++++++++++++++++++++++++++++------------ twilio/twiml.py | 15 ++++++++++ 2 files changed, 64 insertions(+), 18 deletions(-) diff --git a/tests/test_twiml.py b/tests/test_twiml.py index 7bab2ad1e9..781ec1eaf9 100644 --- a/tests/test_twiml.py +++ b/tests/test_twiml.py @@ -367,16 +367,16 @@ def setUp(self): # parse twiml XML string with Element Tree and inspect # structure tree = ET.fromstring(xml) - self.conf = tree.find(".//Queue") + self.queue = tree.find(".//Queue") - def test_conf_text(self): - self.assertEqual(self.conf.text.strip(), "TestQueueAttribute") + def test_queue_text(self): + self.assertEqual(self.queue.text.strip(), "TestQueueAttribute") - def test_conf_waiturl(self): - self.assertEqual(self.conf.get('url'), "") + def test_queue_waiturl(self): + self.assertEqual(self.queue.get('url'), "") - def test_conf_method(self): - self.assertEqual(self.conf.get('method'), "GET") + def test_queue_method(self): + self.assertEqual(self.queue.get('method'), "GET") class TestEnqueue(TwilioTest): @@ -390,22 +390,53 @@ def setUp(self): # parse twiml XML string with Element Tree and inspect # structure tree = ET.fromstring(xml) - self.conf = tree.find("./Enqueue") + self.enqueue = tree.find("./Enqueue") - def test_conf_text(self): - self.assertEqual(self.conf.text.strip(), "TestEnqueueAttribute") + def test_enqueue_text(self): + self.assertEqual(self.enqueue.text.strip(), "TestEnqueueAttribute") - def test_conf_waiturl(self): - self.assertEqual(self.conf.get('waitUrl'), "wait") + def test_enqueue_waiturl(self): + self.assertEqual(self.enqueue.get('waitUrl'), "wait") + + def test_enqueue_method(self): + self.assertEqual(self.enqueue.get('method'), "GET") + + def test_enqueue_action(self): + self.assertEqual(self.enqueue.get('action'), "act") + + def test_enqueue_waitmethod(self): + self.assertEqual(self.enqueue.get('waitUrlMethod'), "POST") + + +class TestEnqueueTask(TwilioTest): + + def setUp(self): + r = Response() + with r.enqueue(None, workflowSid="Workflow1") as e: + e.task('{"selected_language":"en"}', priority="10", timeout="50") + + xml = r.toxml() + + # parse twiml XML string with Element Tree and inspect + # structure + tree = ET.fromstring(xml) + self.enqueue = tree.find("./Enqueue") + self.task = self.enqueue.find("./Task") + + def test_found_task(self): + self.assertIsNotNone(self.task) + + def test_enqueue_workflow_sid(self): + self.assertEqual(self.enqueue.get('workflowSid'), "Workflow1") - def test_conf_method(self): - self.assertEqual(self.conf.get('method'), "GET") + def test_enqueue_task_attributes(self): + self.assertEqual(self.task.text.strip(), '{"selected_language":"en"}') - def test_conf_action(self): - self.assertEqual(self.conf.get('action'), "act") + def test_enqueue_task_priority(self): + self.assertEqual(self.task.get('priority'), "10") - def test_conf_waitmethod(self): - self.assertEqual(self.conf.get('waitUrlMethod'), "POST") + def test_enqueue_task_timeout(self): + self.assertEqual(self.task.get('timeout'), "50") class TestDial(TwilioTest): diff --git a/twilio/twiml.py b/twilio/twiml.py index f05015842d..cbac1122da 100644 --- a/twilio/twiml.py +++ b/twilio/twiml.py @@ -524,10 +524,25 @@ class Enqueue(Verb): GET = 'GET' POST = 'POST' + nestables = ['Task'] + def __init__(self, name, **kwargs): super(Enqueue, self).__init__(**kwargs) self.body = name + def task(self, attributes, **kwargs): + return self.append(Task(attributes, **kwargs)) + + +class Task(Verb): + """Specify the task attributes when enqueuing a call + + :param attributes: Attributes for a task + """ + def __init__(self, attributes, **kwargs): + super(Task, self).__init__(**kwargs) + self.body = attributes + class Leave(Verb): """Signals the call to leave its queue From f0bd64f90b51e866e27bb52c784060af9681bb2b Mon Sep 17 00:00:00 2001 From: gramanathaiah Date: Thu, 6 Aug 2015 14:58:31 -0700 Subject: [PATCH 19/42] Change in package structure and formatting of json to dicts/lists --- docs/usage/taskrouter.rst | 17 ++--- tests/task_router/test_workflow_config.py | 66 ++++++++++--------- twilio/rest/resources/__init__.py | 4 -- twilio/rest/resources/task_router/__init__.py | 14 ---- twilio/task_router/__init__.py | 15 +++++ .../task_router/taskrouter_config.py | 0 .../task_router/workflow_config.py | 0 .../task_router/workflow_rule.py | 0 .../task_router/workflow_ruletarget.py | 0 9 files changed, 57 insertions(+), 59 deletions(-) rename twilio/{rest/resources => }/task_router/taskrouter_config.py (100%) rename twilio/{rest/resources => }/task_router/workflow_config.py (100%) rename twilio/{rest/resources => }/task_router/workflow_rule.py (100%) rename twilio/{rest/resources => }/task_router/workflow_ruletarget.py (100%) diff --git a/docs/usage/taskrouter.rst b/docs/usage/taskrouter.rst index 05566a7719..76de468ed3 100644 --- a/docs/usage/taskrouter.rst +++ b/docs/usage/taskrouter.rst @@ -811,21 +811,16 @@ Using Workflow builder helper classes to create a :class:`Workflow` resource. # See previous examples to create a Workspace WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" - rules =[] - rule_targets=[] - another_rule_targets=[] - rule_target = WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20) - another_rule_target= WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210) - rule_targets.append(rule_target); - another_rule_targets.append(another_rule_target); - rule = WorkflowRule("1==1", rule_targets, "SomeQ") - rules.append(rule) - another_rule = WorkflowRule("1==1", rule_targets1, "SomeOtherQ") - rules.append(another_rule); + rules = [ + WorkflowRule("1==1", [WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20)],"SomeQ"), + WorkflowRule("1==1", [WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210)], "SomeOtherQ") + ] default_target = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916", "1==1", None, None) config = WorkflowConfig(rules, default_target) print config.to_json() + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + workflow = client.workflows(WORKSPACE_SID).create( friendly_name= "Incoming Call Flow", assignment_callback_url= "https://example.com/callback", diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py index d191560e26..d9d70a3dd2 100644 --- a/tests/task_router/test_workflow_config.py +++ b/tests/task_router/test_workflow_config.py @@ -1,50 +1,56 @@ import unittest import json - -from twilio.rest.resources.task_router.workflow_config import WorkflowConfig -from twilio.rest.resources.task_router.workflow_rule import WorkflowRule -from twilio.rest.resources.task_router.workflow_ruletarget import WorkflowRuleTarget +from twilio.task_router.workflow_config import WorkflowConfig +from twilio.task_router.workflow_rule import WorkflowRule +from twilio.task_router.workflow_ruletarget import WorkflowRuleTarget class WorkflowConfigTest(unittest.TestCase): def test_to_json(self): - rules = [] - rule_targets= [] - rule_targets1= [] - rule_target = WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20) - rule_target1 = WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210) - rule_targets.append(rule_target); - rule_targets1.append(rule_target1); - rule = WorkflowRule("1==1", rule_targets, "SomeQ") - rules.append(rule) - rule1 = WorkflowRule("1==1", rule_targets1, "SomeOtherQ") - rules.append(rule1) + rules = [ + WorkflowRule("1==1", [WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20)],"SomeQ"), + WorkflowRule("1==1", [WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210)], "SomeOtherQ") + ] def_target = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916", "1==1", None, None) config = WorkflowConfig(rules , def_target) self.assertEqual(self.is_json(config.to_json()), True) def test_from_json(self): - data = "{\"task_routing\": {\"filters\": [{\"targets\": [{\"queue\": \"WQec62de0e1148b8477f2e24579779c8b1\"," \ - "\"expression\": \"task.language IN worker.languages\"}],\"friendly_name\": \"Sales\",\"expression\": " \ - "\"type == \\\"sales\\\"\"},{\"targets\": [{\"queue\": \"WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f\"," \ - "\"expression\": \"task.language IN worker.languages\"}],\"friendly_name\": \"Marketing\",\"expression\":" \ - " \"type == \\\"marketing\\\"\"},{\"targets\": [{\"queue\": \"WQe5eb317eb23500ade45087ea6522896c\"," \ - "\"expression\": \"task.language IN worker.languages\"}],\"friendly_name\": \"Support\"," \ - "\"expression\": \"type == \\\"support\\\"\"}],\"default_filter\": " \ - "{\"queue\": \"WQ05f810d2d130344fd56e3c91ece2e594\"}}}" - config = WorkflowConfig.json2obj(data) + data = {'task_routing': + {'default_filter': + {'queue': 'WQ05f810d2d130344fd56e3c91ece2e594'}, 'filters': [ + {'expression': 'type == "sales"', 'friendly_name': 'Sales', 'targets': [ + {'queue': 'WQec62de0e1148b8477f2e24579779c8b1', + 'expression': 'task.language IN worker.languages'}]}, + {'expression': 'type == "marketing"', 'friendly_name': 'Marketing', 'targets': + [{'queue': 'WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f', + 'expression': 'task.language IN worker.languages'}] + }, + {'expression': 'type == "support"', 'friendly_name': 'Support', 'targets': + [{'queue': 'WQe5eb317eb23500ade45087ea6522896c', + 'expression': 'task.language IN worker.languages'}] + } + ] + } + } + + config = WorkflowConfig.json2obj(json.dumps(data)) self.assertEqual(len(config.task_routing.filters), 3) self.assertEqual(len(config.task_routing.default_filter), 1) def test_from_json2(self): - data = "{\"task_routing\": {\"default_filter\": {\"expression\": null,\"priority\": null,\"queue\": \"WQYYYYY\"," \ - "\"timeout\": null },\"filters\": [{\"expression\": \"1==1\",\"friendly_name\": \"SomeQ\",\"targets\": [" \ - "{\"expression\": \"1==1\",\"priority\": 1,\"queue\": \"WQXXXX\",\"timeout\": 20}]},{\"expression\": \"1==1\"," \ - "\"friendly_name\": \"SomeOtherQ\",\"targets\": [{\"expression\": \"1==1\",\"priority\": 1,\"queue\": \"WQXXXX\"," \ - "\"timeout\": 20}]}]}}" - config = WorkflowConfig.json2obj(data) + + data = {'task_routing': {'filters': [{'friendly_name': 'SomeQ', 'expression': '1==1', 'targets': [ + {'priority': 1, 'queue': 'WQXXXX', 'expression': '1==1', 'timeout': 20}]}, + {'friendly_name': 'SomeOtherQ', 'expression': '1==1', + 'targets': [ + {'priority': 1, 'queue': 'WQXXXX', 'expression': '1==1', + 'timeout': 20}]}], + 'default_filter': {'priority': None, 'queue': 'WQYYYYY', 'expression': None, + 'timeout': None}}} + config = WorkflowConfig.json2obj(json.dumps(data)) self.assertEqual(len(config.task_routing.filters), 2) self.assertEqual(len(config.task_routing.default_filter), 4) diff --git a/twilio/rest/resources/__init__.py b/twilio/rest/resources/__init__.py index 8a6ecc70c6..fa9aef58ec 100644 --- a/twilio/rest/resources/__init__.py +++ b/twilio/rest/resources/__init__.py @@ -55,16 +55,12 @@ Reservations, Task, Tasks, - TaskRouterConfig, TaskQueue, TaskQueues, Worker, Workers, Workflow, Workflows, - WorkflowConfig, - WorkflowRule, - WorkflowRuleTarget, Workspace, Workspaces, ) diff --git a/twilio/rest/resources/task_router/__init__.py b/twilio/rest/resources/task_router/__init__.py index 61d786781e..5ca394258a 100644 --- a/twilio/rest/resources/task_router/__init__.py +++ b/twilio/rest/resources/task_router/__init__.py @@ -38,17 +38,3 @@ ) -from .taskrouter_config import ( - TaskRouterConfig -) - -from .workflow_config import ( - WorkflowConfig -) - -from .workflow_ruletarget import ( - WorkflowRuleTarget -) -from .workflow_rule import ( - WorkflowRule -) diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index 17975c1cba..bc05e83f75 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -142,3 +142,18 @@ def make_policy(url, method, query_filter=None, post_filter=None, 'query_filter': query_filter or {}, 'post_filter': post_filter or {}, } + +from .taskrouter_config import ( + TaskRouterConfig +) + +from .workflow_config import ( + WorkflowConfig +) + +from .workflow_ruletarget import ( + WorkflowRuleTarget +) +from .workflow_rule import ( + WorkflowRule +) diff --git a/twilio/rest/resources/task_router/taskrouter_config.py b/twilio/task_router/taskrouter_config.py similarity index 100% rename from twilio/rest/resources/task_router/taskrouter_config.py rename to twilio/task_router/taskrouter_config.py diff --git a/twilio/rest/resources/task_router/workflow_config.py b/twilio/task_router/workflow_config.py similarity index 100% rename from twilio/rest/resources/task_router/workflow_config.py rename to twilio/task_router/workflow_config.py diff --git a/twilio/rest/resources/task_router/workflow_rule.py b/twilio/task_router/workflow_rule.py similarity index 100% rename from twilio/rest/resources/task_router/workflow_rule.py rename to twilio/task_router/workflow_rule.py diff --git a/twilio/rest/resources/task_router/workflow_ruletarget.py b/twilio/task_router/workflow_ruletarget.py similarity index 100% rename from twilio/rest/resources/task_router/workflow_ruletarget.py rename to twilio/task_router/workflow_ruletarget.py From 2b691507d0d631fa3ad1416625a03a81ccac3567 Mon Sep 17 00:00:00 2001 From: matt Date: Tue, 11 Aug 2015 10:14:10 -0700 Subject: [PATCH 20/42] Bumping version to 4.5.0 --- CHANGES.md | 10 ++++++++++ twilio/version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 11b8e7dd71..edb4ea582a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,16 @@ twilio-python Changelog Here you can see the full list of changes between each twilio-python release. +Version 4.5.0 +------------- + +Released August 11, 2015: + +- Add support for new Taskrouter JWT Functionality, JWTs now grant access to + - Workspace + - Worker + - TaskQueue + Version 4.4.0 ------------- diff --git a/twilio/version.py b/twilio/version.py index 953ebe5125..a14cd79c93 100644 --- a/twilio/version.py +++ b/twilio/version.py @@ -1,2 +1,2 @@ -__version_info__ = ('4', '4', '0') +__version_info__ = ('4', '5', '0') __version__ = '.'.join(__version_info__) From bb9487f6fcfc1de7e4f728d6831e7a8b19ee632c Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Wed, 26 Aug 2015 16:09:10 -0700 Subject: [PATCH 21/42] Allow fetching reservations by worker --- tests/task_router/test_reservations.py | 13 +++++++++++++ twilio/rest/task_router.py | 15 ++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/tests/task_router/test_reservations.py b/tests/task_router/test_reservations.py index ff85ffa316..5d635dad9a 100644 --- a/tests/task_router/test_reservations.py +++ b/tests/task_router/test_reservations.py @@ -8,6 +8,7 @@ AUTH = ("AC123", "token") BASE_URI = "https://taskrouter.twilio.com/v1/Accounts/AC123/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Tasks/WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +BASE_WORKER_URI = "https://taskrouter.twilio.com/v1/Accounts/AC123/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Workers/WKaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" RESERVATION_SID = "WRaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" @@ -36,6 +37,18 @@ def test_list(self, request): request.assert_called_with("GET", uri, params={}, auth=AUTH, use_json_extension=False) + @patch('twilio.rest.resources.base.make_twilio_request') + def test_list_for_worker(self, request): + resp = create_mock_json('tests/resources/task_router/reservations_list.json') + resp.status_code = 200 + request.return_value = resp + + uri = "{0}/Reservations".format(BASE_WORKER_URI) + list_resource = Reservations(BASE_WORKER_URI, AUTH) + list_resource.list() + request.assert_called_with("GET", uri, params={}, auth=AUTH, + use_json_extension=False) + @patch('twilio.rest.resources.base.make_twilio_request') def test_update_instance(self, request): resp = create_mock_json('tests/resources/task_router/reservations_instance.json') diff --git a/twilio/rest/task_router.py b/twilio/rest/task_router.py index f9672d2820..b7d37b73fe 100644 --- a/twilio/rest/task_router.py +++ b/twilio/rest/task_router.py @@ -52,13 +52,18 @@ def events(self, workspace_sid): base_uri = "{0}/{1}".format(self.workspace_uri, workspace_sid) return Events(base_uri, self.auth, self.timeout) - def reservations(self, workspace_sid, task_sid): + def reservations(self, workspace_sid, sid): """ Return a :class:`Reservations` instance for the :class:`Reservation` - with the given workspace_sid ans task_sid - """ - base_uri = "{0}/{1}/Tasks/{2}".format(self.workspace_uri, - workspace_sid, task_sid) + with the given workspace_sid and an task_sid or worker_sid + """ + if sid.startswith('WT'): + base_uri = "{0}/{1}/Tasks/{2}".format(self.workspace_uri, + workspace_sid, sid) + elif sid.startswith('WK'): + base_uri = "{0}/{1}/Workers/{2}".format(self.workspace_uri, + workspace_sid, sid) + return Reservations(base_uri, self.auth, self.timeout) def task_queues(self, workspace_sid): From 2c0acb1398a75e8a74e66889c477af4fcd0f5089 Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Fri, 28 Aug 2015 17:09:40 -0700 Subject: [PATCH 22/42] Add nested lookup for Task --- tests/test_twiml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_twiml.py b/tests/test_twiml.py index 781ec1eaf9..e995c418ac 100644 --- a/tests/test_twiml.py +++ b/tests/test_twiml.py @@ -421,7 +421,7 @@ def setUp(self): # structure tree = ET.fromstring(xml) self.enqueue = tree.find("./Enqueue") - self.task = self.enqueue.find("./Task") + self.task = self.enqueue.find(".//Task") def test_found_task(self): self.assertIsNotNone(self.task) From b832bbc528583283c756a97efb7cca1e130e1d0f Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Thu, 3 Sep 2015 11:27:11 -0700 Subject: [PATCH 23/42] Add Reservations as a sub resource of worker --- twilio/rest/resources/task_router/workers.py | 4 +++- twilio/rest/task_router.py | 14 +++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/twilio/rest/resources/task_router/workers.py b/twilio/rest/resources/task_router/workers.py index f257435cb9..6383a57d0c 100644 --- a/twilio/rest/resources/task_router/workers.py +++ b/twilio/rest/resources/task_router/workers.py @@ -1,5 +1,6 @@ from .. import NextGenInstanceResource, NextGenListResource from .statistics import Statistics +from .reservations import Reservations class Worker(NextGenInstanceResource): @@ -68,7 +69,8 @@ class Worker(NextGenInstanceResource): calculate :class: `Workflow` statistics. """ subresources = [ - Statistics + Statistics, + Reservations ] def delete(self): diff --git a/twilio/rest/task_router.py b/twilio/rest/task_router.py index b7d37b73fe..b33414cc02 100644 --- a/twilio/rest/task_router.py +++ b/twilio/rest/task_router.py @@ -52,17 +52,13 @@ def events(self, workspace_sid): base_uri = "{0}/{1}".format(self.workspace_uri, workspace_sid) return Events(base_uri, self.auth, self.timeout) - def reservations(self, workspace_sid, sid): + def reservations(self, workspace_sid, task_sid): """ Return a :class:`Reservations` instance for the :class:`Reservation` - with the given workspace_sid and an task_sid or worker_sid - """ - if sid.startswith('WT'): - base_uri = "{0}/{1}/Tasks/{2}".format(self.workspace_uri, - workspace_sid, sid) - elif sid.startswith('WK'): - base_uri = "{0}/{1}/Workers/{2}".format(self.workspace_uri, - workspace_sid, sid) + with the given workspace_sid ans task_sid + """ + base_uri = "{0}/{1}/Tasks/{2}".format(self.workspace_uri, + workspace_sid, task_sid) return Reservations(base_uri, self.auth, self.timeout) From 097df1976981ade934bba03e3c64e977b6845141 Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Thu, 3 Sep 2015 11:28:14 -0700 Subject: [PATCH 24/42] Cleanup extra newline --- twilio/rest/task_router.py | 1 - 1 file changed, 1 deletion(-) diff --git a/twilio/rest/task_router.py b/twilio/rest/task_router.py index b33414cc02..f9672d2820 100644 --- a/twilio/rest/task_router.py +++ b/twilio/rest/task_router.py @@ -59,7 +59,6 @@ def reservations(self, workspace_sid, task_sid): """ base_uri = "{0}/{1}/Tasks/{2}".format(self.workspace_uri, workspace_sid, task_sid) - return Reservations(base_uri, self.auth, self.timeout) def task_queues(self, workspace_sid): From f82ec7ff5bd3904865fb30d05f0da6f1c948adaa Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Thu, 3 Sep 2015 11:37:44 -0700 Subject: [PATCH 25/42] Fix assertion order --- tests/task_router/test_workflow_config.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py index d9d70a3dd2..6aefd48a93 100644 --- a/tests/task_router/test_workflow_config.py +++ b/tests/task_router/test_workflow_config.py @@ -14,7 +14,7 @@ def test_to_json(self): ] def_target = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916", "1==1", None, None) config = WorkflowConfig(rules , def_target) - self.assertEqual(self.is_json(config.to_json()), True) + self.assertEqual(True, self.is_json(config.to_json())) def test_from_json(self): @@ -37,8 +37,8 @@ def test_from_json(self): } config = WorkflowConfig.json2obj(json.dumps(data)) - self.assertEqual(len(config.task_routing.filters), 3) - self.assertEqual(len(config.task_routing.default_filter), 1) + self.assertEqual(3, len(config.task_routing.filters)) + self.assertEqual(1, len(config.task_routing.default_filter)) def test_from_json2(self): @@ -51,8 +51,8 @@ def test_from_json2(self): 'default_filter': {'priority': None, 'queue': 'WQYYYYY', 'expression': None, 'timeout': None}}} config = WorkflowConfig.json2obj(json.dumps(data)) - self.assertEqual(len(config.task_routing.filters), 2) - self.assertEqual(len(config.task_routing.default_filter), 4) + self.assertEqual(2, len(config.task_routing.filters)) + self.assertEqual(4, len(config.task_routing.default_filter)) def is_json(self, myjson): try: From 1ce0abce00c1cdfe1cbabdaba2bafe3a673e9de8 Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Thu, 3 Sep 2015 12:08:51 -0700 Subject: [PATCH 26/42] Fix merge conflict --- twilio/task_router/__init__.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index d940e5a7bb..3b501a8cbc 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -247,13 +247,6 @@ def __init__(self, account_sid, auth_token, workspace_sid): def setup_resource(self): self.resource_url = self.base_url - return { - 'url': url, - 'method': method, - 'allow': allowed, - 'query_filter': query_filter or {}, - 'post_filter': post_filter or {}, - } from .taskrouter_config import ( TaskRouterConfig From f1958bcc57a6e8f3897c47e989d9585281508505 Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Thu, 3 Sep 2015 12:09:43 -0700 Subject: [PATCH 27/42] Fix make test (validation on styling) --- tests/task_router/test_workflow_config.py | 65 +++++++++++++------ twilio/rest/resources/task_router/__init__.py | 2 - twilio/task_router/workflow_config.py | 8 ++- twilio/task_router/workflow_rule.py | 1 - twilio/task_router/workflow_ruletarget.py | 1 - 5 files changed, 51 insertions(+), 26 deletions(-) diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py index 6aefd48a93..e98aea5705 100644 --- a/tests/task_router/test_workflow_config.py +++ b/tests/task_router/test_workflow_config.py @@ -9,32 +9,56 @@ class WorkflowConfigTest(unittest.TestCase): def test_to_json(self): rules = [ - WorkflowRule("1==1", [WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20)],"SomeQ"), - WorkflowRule("1==1", [WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210)], "SomeOtherQ") + WorkflowRule("1==1", [WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20)], "SomeQ"), + WorkflowRule("1==1", [WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210)], "SomeOtherQ") ] def_target = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916", "1==1", None, None) - config = WorkflowConfig(rules , def_target) + config = WorkflowConfig(rules, def_target) self.assertEqual(True, self.is_json(config.to_json())) def test_from_json(self): - data = {'task_routing': - {'default_filter': - {'queue': 'WQ05f810d2d130344fd56e3c91ece2e594'}, 'filters': [ - {'expression': 'type == "sales"', 'friendly_name': 'Sales', 'targets': [ - {'queue': 'WQec62de0e1148b8477f2e24579779c8b1', - 'expression': 'task.language IN worker.languages'}]}, - {'expression': 'type == "marketing"', 'friendly_name': 'Marketing', 'targets': - [{'queue': 'WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f', - 'expression': 'task.language IN worker.languages'}] - }, - {'expression': 'type == "support"', 'friendly_name': 'Support', 'targets': - [{'queue': 'WQe5eb317eb23500ade45087ea6522896c', - 'expression': 'task.language IN worker.languages'}] - } - ] - } - } + data = { + 'task_routing': + { + 'filters': [ + { + 'expression': 'type == "sales"', + 'friendly_name': 'Sales', + 'targets': [ + { + 'queue': 'WQec62de0e1148b8477f2e24579779c8b1', + 'expression': 'task.language IN worker.languages' + } + ] + }, + { + 'expression': 'type == "marketing"', + 'friendly_name': 'Marketing', + 'targets': [ + { + 'queue': 'WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f', + 'expression': 'task.language IN worker.languages' + } + ] + }, + { + 'expression': 'type == "support"', + 'friendly_name': 'Support', + 'targets': [ + { + 'queue': 'WQe5eb317eb23500ade45087ea6522896c', + 'expression': 'task.language IN worker.languages' + } + ] + } + ], + 'default_filter': + { + 'queue': 'WQ05f810d2d130344fd56e3c91ece2e594' + } + } + } config = WorkflowConfig.json2obj(json.dumps(data)) self.assertEqual(3, len(config.task_routing.filters)) @@ -58,5 +82,6 @@ def is_json(self, myjson): try: json.loads(myjson) except ValueError, e: + print e return False return True diff --git a/twilio/rest/resources/task_router/__init__.py b/twilio/rest/resources/task_router/__init__.py index 5ca394258a..e312f1a882 100644 --- a/twilio/rest/resources/task_router/__init__.py +++ b/twilio/rest/resources/task_router/__init__.py @@ -36,5 +36,3 @@ Workspace, Workspaces ) - - diff --git a/twilio/task_router/workflow_config.py b/twilio/task_router/workflow_config.py index e79881b3f7..e9c27aa378 100644 --- a/twilio/task_router/workflow_config.py +++ b/twilio/task_router/workflow_config.py @@ -14,9 +14,13 @@ def __init__(self, workflow_rules, default_target): self.task_routing = TaskRouterConfig(workflow_rules, default_target) def to_json(self): - return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4) + return json.dumps(self, + default=lambda o: o.__dict__, + sort_keys=True, + indent=4) @staticmethod def json2obj(data): m = json.loads(data) - return WorkflowConfig(m['task_routing']['filters'], m['task_routing']['default_filter']) + return WorkflowConfig(m['task_routing']['filters'], + m['task_routing']['default_filter']) diff --git a/twilio/task_router/workflow_rule.py b/twilio/task_router/workflow_rule.py index 92a74b5bb9..3cae68ec80 100644 --- a/twilio/task_router/workflow_rule.py +++ b/twilio/task_router/workflow_rule.py @@ -32,4 +32,3 @@ def __repr__(self): 'friendly_name': self.friendly_name, 'target': self.target, }) - diff --git a/twilio/task_router/workflow_ruletarget.py b/twilio/task_router/workflow_ruletarget.py index 987ac0f911..1cee506c30 100644 --- a/twilio/task_router/workflow_ruletarget.py +++ b/twilio/task_router/workflow_ruletarget.py @@ -25,4 +25,3 @@ def __init__(self, queue, expression, priority, timeout): self.expression = expression self.priority = priority self.timeout = timeout - From d242629dcf7574e66d14f664ecb589d0c8d457ef Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Thu, 3 Sep 2015 12:16:12 -0700 Subject: [PATCH 28/42] Add worker_reservations endpoint --- twilio/rest/task_router.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/twilio/rest/task_router.py b/twilio/rest/task_router.py index f9672d2820..a03ef2e498 100644 --- a/twilio/rest/task_router.py +++ b/twilio/rest/task_router.py @@ -61,6 +61,15 @@ def reservations(self, workspace_sid, task_sid): workspace_sid, task_sid) return Reservations(base_uri, self.auth, self.timeout) + def worker_reservations(self, workspace_sid, worker_sid): + """ + Return a :class:`Reservations` instance for the :class:`Reservation` + with the given workspace_sid ans worker_sid + """ + base_uri = "{0}/{1}/Workers/{2}".format(self.workspace_uri, + workspace_sid, worker_sid) + return Reservations(base_uri, self.auth, self.timeout) + def task_queues(self, workspace_sid): """ Return a :class:`TaskQueues` instance for the :class:`TaskQueue` with From 6eae914ae3dd77750fa437466161d61e81c216fb Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Tue, 22 Sep 2015 16:29:22 -0700 Subject: [PATCH 29/42] Fix indentation --- twilio/rest/task_router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twilio/rest/task_router.py b/twilio/rest/task_router.py index a03ef2e498..eec74857e8 100644 --- a/twilio/rest/task_router.py +++ b/twilio/rest/task_router.py @@ -67,7 +67,7 @@ def worker_reservations(self, workspace_sid, worker_sid): with the given workspace_sid ans worker_sid """ base_uri = "{0}/{1}/Workers/{2}".format(self.workspace_uri, - workspace_sid, worker_sid) + workspace_sid, worker_sid) return Reservations(base_uri, self.auth, self.timeout) def task_queues(self, workspace_sid): From 7978efd5e36921818c7295521d56c4e0c7e7a753 Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Tue, 22 Sep 2015 16:29:41 -0700 Subject: [PATCH 30/42] Remove --use-mirrors from test-install --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9f51e8826a..68d3c452e8 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ venv: virtualenv venv install: venv - . venv/bin/activate; pip install . --use-mirrors + . venv/bin/activate; pip install . test-install: install . venv/bin/activate; pip install -r tests/requirements.txt From d3380d5dd8a67610f7d0d7c06e6e146c51a57373 Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Tue, 22 Sep 2015 17:32:37 -0700 Subject: [PATCH 31/42] Add parameter to use container-based travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index a7803a70da..0e9bbdfa2e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,3 +13,4 @@ script: - flake8 --ignore=F401 twilio - flake8 --ignore=E123,E126,E128,E501 tests - nosetests +sudo: false From a51d23f9ce610d7cee5051c0dd451f33bc65fb0b Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Tue, 22 Sep 2015 17:40:09 -0700 Subject: [PATCH 32/42] Add secure travis token --- .travis.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0e9bbdfa2e..75a7c3dc44 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,19 @@ language: python python: - - "2.6" - - "2.7" - - "3.2" - - "3.3" - - "3.4" + - '2.6' + - '2.7' + - '3.2' + - '3.3' + - '3.4' install: - pip install . --use-mirrors - pip install -r requirements.txt --use-mirrors - pip install -r tests/requirements.txt --use-mirrors -script: +script: - flake8 --ignore=F401 twilio - flake8 --ignore=E123,E126,E128,E501 tests - nosetests sudo: false +notifications: + slack: + secure: Axcfefsnbvly090AqDg3IL87fRzPEf9SXIc9JG5D1qiNekqMJaFz6CoLciSY1ydJKSKLygqB2TCKwPzZGwsIIxcelx2SVodFsjDjjbyHsUE2V9K7hET5eS9BX/aPgT/zoMq/0LBK+U3/9ssg07cCtck7wc+4mBd76UJ5sc37uQY= From 61d4746a10cf5ec1f0aec596798f2c5fcf12b174 Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 23 Sep 2015 10:50:28 -0700 Subject: [PATCH 33/42] Fix syntax errors for non-2.7 --- tests/task_router/test_workflow_config.py | 2 +- twilio/task_router/__init__.py | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py index e98aea5705..204a0709b1 100644 --- a/tests/task_router/test_workflow_config.py +++ b/tests/task_router/test_workflow_config.py @@ -81,7 +81,7 @@ def test_from_json2(self): def is_json(self, myjson): try: json.loads(myjson) - except ValueError, e: + except ValueError as e: print e return False return True diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index 3b501a8cbc..c77feeca29 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -16,7 +16,7 @@ def deprecated(func): def log_warning(*args, **kwargs): # stacklevel = 2 makes the warning refer to the caller of the # deprecation rather than the source of deprecation itself - warnings.warn("Call to deprecated function {}.". + warnings.warn("Call to deprecated function {0}.". format(func.__name__), stacklevel=2, category=DeprecationWarning) @@ -32,9 +32,9 @@ def __init__(self, account_sid, auth_token, workspace_sid, channel_id): self.workspace_sid = workspace_sid self.channel_id = channel_id - self.base_url = "{}/{}/Workspaces/{}".format(TASK_ROUTER_BASE_URL, - TASK_ROUTER_VERSION, - workspace_sid) + self.base_url = "{0}/{1}/Workspaces/{2}".format(TASK_ROUTER_BASE_URL, + TASK_ROUTER_VERSION, + workspace_sid) # validate the JWT self.validate_jwt() @@ -65,12 +65,13 @@ def setup_resource(self): self.allow(reservations_url, "GET") elif self.channel_prefix == "WQ": - self.resource_url = "{}/TaskQueues/{}".format( + self.resource_url = "{0}/TaskQueues/{1}".format( self.base_url, self.channel_id) def allow_web_sockets(self, channel_id): - web_socket_url = "{}/{}/{}".format(TASK_ROUTER_BASE_EVENTS_URL, - self.account_sid, self.channel_id) + web_socket_url = "{0}/{1}/{2}".format(TASK_ROUTER_BASE_EVENTS_URL, + self.account_sid, + self.channel_id) self.policies.append(self.make_policy(web_socket_url, "GET", True)) self.policies.append(self.make_policy(web_socket_url, "POST", True)) From 8e553c4005aaf2afc8a0a3ec75d88f82e4f7ca63 Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 23 Sep 2015 10:55:48 -0700 Subject: [PATCH 34/42] Only build master automatically --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 75a7c3dc44..da578cee53 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,9 @@ script: - flake8 --ignore=E123,E126,E128,E501 tests - nosetests sudo: false +branches: + only: + -master notifications: slack: secure: Axcfefsnbvly090AqDg3IL87fRzPEf9SXIc9JG5D1qiNekqMJaFz6CoLciSY1ydJKSKLygqB2TCKwPzZGwsIIxcelx2SVodFsjDjjbyHsUE2V9K7hET5eS9BX/aPgT/zoMq/0LBK+U3/9ssg07cCtck7wc+4mBd76UJ5sc37uQY= From 7593d7f87700ff3f641f90923e4d2ad693f36137 Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 23 Sep 2015 11:44:17 -0700 Subject: [PATCH 35/42] Remove calls to assertIsNotNone This call does not exist < 2.7 --- tests/task_router/test_capability.py | 12 ++++----- .../test_task_router_capability.py | 16 ++++++------ .../test_task_router_taskqueue_capability.py | 24 ++++++++--------- .../test_task_router_worker_capability.py | 26 +++++++++---------- .../test_task_router_workspace_capability.py | 24 ++++++++--------- tests/test_twiml.py | 2 +- 6 files changed, 52 insertions(+), 52 deletions(-) diff --git a/tests/task_router/test_capability.py b/tests/task_router/test_capability.py index eb4d15edba..de105db281 100644 --- a/tests/task_router/test_capability.py +++ b/tests/task_router/test_capability.py @@ -55,7 +55,7 @@ def test_defaults(self): self.assertTrue(decoded is not None) websocket_url = ( - 'https://event-bridge.twilio.com/v1/wschannels/{}/{}'.format(self.account_sid, self.worker_sid) + 'https://event-bridge.twilio.com/v1/wschannels/{0}/{1}'.format(self.account_sid, self.worker_sid) ) expected = [ { @@ -66,7 +66,7 @@ def test_defaults(self): 'post_filter': {}, }, { - 'url': 'https://taskrouter.twilio.com/v1/Workspaces/{}/Tasks/**'.format(self.workspace_sid), + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Tasks/**'.format(self.workspace_sid), 'method': 'GET', 'allow': True, 'query_filter': {}, @@ -87,7 +87,7 @@ def test_defaults(self): 'post_filter': {}, }, { - 'url': 'https://taskrouter.twilio.com/v1/Workspaces/{}/Workers/{}'.format(self.workspace_sid, self.worker_sid), + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}'.format(self.workspace_sid, self.worker_sid), 'method': 'GET', 'allow': True, 'query_filter': {}, @@ -102,7 +102,7 @@ def test_allow_worker_activity_updates(self): decoded = jwt.decode(token, self.auth_token) self.assertTrue(decoded is not None) - url = 'https://taskrouter.twilio.com/v1/Workspaces/{}/Workers/{}'.format( + url = 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}'.format( self.workspace_sid, self.worker_sid, ) @@ -122,7 +122,7 @@ def test_allow_worker_fetch_attributes(self): decoded = jwt.decode(token, self.auth_token) self.assertTrue(decoded is not None) - url = 'https://taskrouter.twilio.com/v1/Workspaces/{}/Workers/{}'.format( + url = 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}'.format( self.workspace_sid, self.worker_sid, ) @@ -143,7 +143,7 @@ def test_allow_task_reservation_updates(self): decoded = jwt.decode(token, self.auth_token) self.assertTrue(decoded is not None) - url = 'https://taskrouter.twilio.com/v1/Workspaces/{}/Tasks/**'.format( + url = 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Tasks/**'.format( self.workspace_sid, ) diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py index a32768bfbf..78116c0db1 100644 --- a/tests/task_router/test_task_router_capability.py +++ b/tests/task_router/test_task_router_capability.py @@ -37,10 +37,10 @@ def test_workspace_default(self): capability.generate_token() token = capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.check_decoded(decoded, account_sid, workspace_sid, channel_id) @@ -64,10 +64,10 @@ def test_worker_default(self): capability.generate_token() token = capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.check_decoded(decoded, account_sid, workspace_sid, worker_sid, worker_sid) @@ -93,10 +93,10 @@ def test_task_queue_default(self): capability.generate_token() token = capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.check_decoded(decoded, account_sid, workspace_sid, taskqueue_sid, taskqueue_sid) @@ -120,10 +120,10 @@ def test_deprecated_worker(self): capability.generate_token() token = capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.check_decoded(decoded, account_sid, workspace_sid, worker_sid, worker_sid) diff --git a/tests/task_router/test_task_router_taskqueue_capability.py b/tests/task_router/test_task_router_taskqueue_capability.py index 66f466a763..42ed9597e6 100644 --- a/tests/task_router/test_task_router_taskqueue_capability.py +++ b/tests/task_router/test_task_router_taskqueue_capability.py @@ -17,10 +17,10 @@ def setUp(self): def test_generate_token(self): token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.assertEqual(decoded["iss"], self.account_sid) self.assertEqual(decoded["account_sid"], self.account_sid) @@ -32,10 +32,10 @@ def test_generate_token(self): def test_generate_token_with_default_ttl(self): token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.assertEqual(int(time.time()) + 3600, decoded["exp"]) @@ -43,19 +43,19 @@ def test_generate_token_with_custom_ttl(self): ttl = 10000 token = self.capability.generate_token(ttl) - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.assertEqual(int(time.time()) + 10000, decoded["exp"]) def test_default(self): token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) policies = decoded['policies'] self.assertEqual(len(policies), 3) @@ -88,10 +88,10 @@ def test_allow_fetch_subresources(self): self.capability.allow_fetch_subresources() token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) policies = decoded['policies'] self.assertEqual(len(policies), 4) @@ -110,10 +110,10 @@ def test_allow_updates_subresources(self): self.capability.allow_updates_subresources() token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) policies = decoded['policies'] self.assertEqual(len(policies), 4) diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py index 4f7578d91b..e2edaa64b5 100644 --- a/tests/task_router/test_task_router_worker_capability.py +++ b/tests/task_router/test_task_router_worker_capability.py @@ -36,19 +36,19 @@ def setUp(self): def test_generate_token(self): token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.check_decoded(decoded, self.account_sid, self.workspace_sid, self.worker_sid, self.worker_sid) def test_generate_token_with_default_ttl(self): token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.assertEqual(int(time.time()) + 3600, decoded["exp"]) @@ -56,19 +56,19 @@ def test_generate_token_with_custom_ttl(self): ttl = 10000 token = self.capability.generate_token(ttl) - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.assertEqual(int(time.time()) + 10000, decoded["exp"]) def test_defaults(self): token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) websocket_url = 'https://event-bridge.twilio.com/v1/wschannels/{}/{}'.format(self.account_sid, self.worker_sid) @@ -92,10 +92,10 @@ def test_allow_activity_updates(self): self.capability.allow_activity_updates() token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) policies = decoded['policies'] self.assertEqual(len(policies), 6) @@ -106,7 +106,7 @@ def test_allow_activity_updates(self): self.assertEqual(url, policy["url"]) self.assertEqual("POST", policy["method"]) self.assertTrue(policy["allow"]) - self.assertIsNotNone(policy['post_filter']) + self.assertNotEqual(None, policy['post_filter']) self.assertEqual({}, policy['query_filter']) self.assertTrue(policy['post_filter']['ActivitySid']) @@ -115,10 +115,10 @@ def test_allow_reservation_updates(self): self.capability.allow_reservation_updates() token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) policies = decoded['policies'] self.assertEqual(len(policies), 6) diff --git a/tests/task_router/test_task_router_workspace_capability.py b/tests/task_router/test_task_router_workspace_capability.py index 2767afd59d..048611166e 100644 --- a/tests/task_router/test_task_router_workspace_capability.py +++ b/tests/task_router/test_task_router_workspace_capability.py @@ -35,19 +35,19 @@ def setUp(self): def test_generate_token(self): token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.check_decoded(decoded, self.account_sid, self.workspace_sid, self.workspace_sid) def test_generate_token_with_default_ttl(self): token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.assertEqual(int(time.time()) + 3600, decoded["exp"]) @@ -55,19 +55,19 @@ def test_generate_token_with_custom_ttl(self): ttl = 10000 token = self.capability.generate_token(ttl) - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.assertEqual(int(time.time()) + 10000, decoded["exp"]) def test_default(self): token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) policies = decoded['policies'] self.assertEqual(len(policies), 3) @@ -83,10 +83,10 @@ def test_allow_fetch_subresources(self): self.capability.allow_fetch_subresources() token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) policies = decoded['policies'] self.assertEqual(len(policies), 4) @@ -100,10 +100,10 @@ def test_allow_updates_subresources(self): self.capability.allow_updates_subresources() token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) policies = decoded['policies'] self.assertEqual(len(policies), 4) diff --git a/tests/test_twiml.py b/tests/test_twiml.py index e995c418ac..c022baeda0 100644 --- a/tests/test_twiml.py +++ b/tests/test_twiml.py @@ -424,7 +424,7 @@ def setUp(self): self.task = self.enqueue.find(".//Task") def test_found_task(self): - self.assertIsNotNone(self.task) + self.assertNotEqual(None, self.task) def test_enqueue_workflow_sid(self): self.assertEqual(self.enqueue.get('workflowSid'), "Workflow1") From 7fe52434ed264e7d7ccf6e62c32489d658ec4646 Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 23 Sep 2015 11:58:14 -0700 Subject: [PATCH 36/42] Remove branches restriction from travis --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index da578cee53..75a7c3dc44 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,9 +14,6 @@ script: - flake8 --ignore=E123,E126,E128,E501 tests - nosetests sudo: false -branches: - only: - -master notifications: slack: secure: Axcfefsnbvly090AqDg3IL87fRzPEf9SXIc9JG5D1qiNekqMJaFz6CoLciSY1ydJKSKLygqB2TCKwPzZGwsIIxcelx2SVodFsjDjjbyHsUE2V9K7hET5eS9BX/aPgT/zoMq/0LBK+U3/9ssg07cCtck7wc+4mBd76UJ5sc37uQY= From 96704483b7821f40531fa494181ae4e0214fcb0a Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 23 Sep 2015 12:00:20 -0700 Subject: [PATCH 37/42] Fix rogue format --- tests/task_router/test_task_router_worker_capability.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py index e2edaa64b5..83feaf6b8c 100644 --- a/tests/task_router/test_task_router_worker_capability.py +++ b/tests/task_router/test_task_router_worker_capability.py @@ -70,7 +70,7 @@ def test_defaults(self): decoded = jwt.decode(token, self.auth_token) self.assertNotEqual(None, decoded) - websocket_url = 'https://event-bridge.twilio.com/v1/wschannels/{}/{}'.format(self.account_sid, self.worker_sid) + websocket_url = 'https://event-bridge.twilio.com/v1/wschannels/{0}/{1}'.format(self.account_sid, self.worker_sid) # expect 5 policies policies = decoded['policies'] @@ -101,7 +101,7 @@ def test_allow_activity_updates(self): self.assertEqual(len(policies), 6) policy = policies[5] - url = "https://taskrouter.twilio.com/v1/Workspaces/{}/Workers/{}".format(self.workspace_sid, self.worker_sid) + url = "https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}".format(self.workspace_sid, self.worker_sid) self.assertEqual(url, policy["url"]) self.assertEqual("POST", policy["method"]) @@ -125,7 +125,7 @@ def test_allow_reservation_updates(self): policy = policies[5] - url = "https://taskrouter.twilio.com/v1/Workspaces/{}/Tasks/**".format(self.workspace_sid) + url = "https://taskrouter.twilio.com/v1/Workspaces/{0}/Tasks/**".format(self.workspace_sid) self.check_policy('POST', url, policy) From b1af810d740198851cbdacf890d22e48c6b6c17e Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 23 Sep 2015 12:03:49 -0700 Subject: [PATCH 38/42] Functionalize print --- tests/task_router/test_workflow_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py index 204a0709b1..458fecc045 100644 --- a/tests/task_router/test_workflow_config.py +++ b/tests/task_router/test_workflow_config.py @@ -82,6 +82,6 @@ def is_json(self, myjson): try: json.loads(myjson) except ValueError as e: - print e + print(e) return False return True From 12dbd57a73de9bf977f56db69164f57f729e388b Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 23 Sep 2015 12:18:17 -0700 Subject: [PATCH 39/42] Bump to version 4.6.0 --- CHANGES.md | 9 +++++++++ twilio/version.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index edb4ea582a..94e2225e03 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,15 @@ twilio-python Changelog Here you can see the full list of changes between each twilio-python release. +Version 4.6.0 +------------- + +Released September 23, 2015: + +- Allow fetching TaskRouter reservations by Worker +- Add missing Enqueue->Task TwiML generation +- Add Worflow construction + Version 4.5.0 ------------- diff --git a/twilio/version.py b/twilio/version.py index a14cd79c93..9e8a4df44c 100644 --- a/twilio/version.py +++ b/twilio/version.py @@ -1,2 +1,2 @@ -__version_info__ = ('4', '5', '0') +__version_info__ = ('4', '6', '0') __version__ = '.'.join(__version_info__) From a25329c0851a080561e00595a4f4e7815e3bb2fa Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 23 Sep 2015 14:28:44 -0700 Subject: [PATCH 40/42] Reconfigure travis --- .travis.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 75a7c3dc44..d1e18d2a59 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,4 +16,9 @@ script: sudo: false notifications: slack: - secure: Axcfefsnbvly090AqDg3IL87fRzPEf9SXIc9JG5D1qiNekqMJaFz6CoLciSY1ydJKSKLygqB2TCKwPzZGwsIIxcelx2SVodFsjDjjbyHsUE2V9K7hET5eS9BX/aPgT/zoMq/0LBK+U3/9ssg07cCtck7wc+4mBd76UJ5sc37uQY= + on_success: change + on_failure: change + rooms: + secure: + - TcDlBcKXtqmMdVsa3Lsofdqc1uVjqhZouwNETC260rByRb74gTHGZ1Da7PRkv+AZIFUq7S1uWTZXTXJTm154hi4aQb9SE2UowVwTJMjIKyy4P79s1eoyADP92cFEcpqSs4iVpU3t5srTj8cg2fVks8EsV5pDVJut1oBH4qYJEZw= + - qqtcwaS0y5e2SVm5g/zSRMgo7FcZ8Oa44bxQUDvJh/84/DHMD3zZoAv/A4+Vlbs0tCjnSKxMDuLxTzpiPgru4KPH7yj4fEXf7+sfwiLD//WBVWpGMYLa+PNCGS6hhnAuFkA2psZCmmzkbJbX0N03EdWiWFzV79WPgNI+WzpYIVQ= From 636bf915c3945d01a8acab85f0c7e90b48239d80 Mon Sep 17 00:00:00 2001 From: Kevin Whinnery Date: Wed, 23 Sep 2015 16:39:02 -0500 Subject: [PATCH 41/42] fix broken image link --- docs/appengine.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/appengine.rst b/docs/appengine.rst index 0cdf9db9c8..74a26a4fcd 100644 --- a/docs/appengine.rst +++ b/docs/appengine.rst @@ -102,7 +102,7 @@ The key is to lay out your project in a way that makes sense. the settings you want in Google App Engine - Note the folder path ends with ``twilio-demo/src``. - .. image:: https://www.evernote.com/shard/s265/sh/1b9407b0-c89b-464d-b352-dbf8fc7a7f41/f536b8e79747f43220fc12e0e0026ee2/res/5b2f83af-8a7f-451f-afba-db092c55aa44/skitch.png + .. image:: http://howtodocs.s3.amazonaws.com/helpers/appengine.png Once App Engine is running locally, in your browser, you should be able to navigate to ``http://localhost`` + the provided Port and view the twilio From 9f62d187176bfa6f7161d1166c8a984c7fea5d89 Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 23 Sep 2015 15:07:20 -0700 Subject: [PATCH 42/42] Fix travis config --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d1e18d2a59..250dcb8c18 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,5 @@ notifications: on_success: change on_failure: change rooms: - secure: - - TcDlBcKXtqmMdVsa3Lsofdqc1uVjqhZouwNETC260rByRb74gTHGZ1Da7PRkv+AZIFUq7S1uWTZXTXJTm154hi4aQb9SE2UowVwTJMjIKyy4P79s1eoyADP92cFEcpqSs4iVpU3t5srTj8cg2fVks8EsV5pDVJut1oBH4qYJEZw= - - qqtcwaS0y5e2SVm5g/zSRMgo7FcZ8Oa44bxQUDvJh/84/DHMD3zZoAv/A4+Vlbs0tCjnSKxMDuLxTzpiPgru4KPH7yj4fEXf7+sfwiLD//WBVWpGMYLa+PNCGS6hhnAuFkA2psZCmmzkbJbX0N03EdWiWFzV79WPgNI+WzpYIVQ= + - secure: TcDlBcKXtqmMdVsa3Lsofdqc1uVjqhZouwNETC260rByRb74gTHGZ1Da7PRkv+AZIFUq7S1uWTZXTXJTm154hi4aQb9SE2UowVwTJMjIKyy4P79s1eoyADP92cFEcpqSs4iVpU3t5srTj8cg2fVks8EsV5pDVJut1oBH4qYJEZw= + - secure: qqtcwaS0y5e2SVm5g/zSRMgo7FcZ8Oa44bxQUDvJh/84/DHMD3zZoAv/A4+Vlbs0tCjnSKxMDuLxTzpiPgru4KPH7yj4fEXf7+sfwiLD//WBVWpGMYLa+PNCGS6hhnAuFkA2psZCmmzkbJbX0N03EdWiWFzV79WPgNI+WzpYIVQ=