From ca1f26e35c4f0f9dd0d9b8318e677c770fcae3e0 Mon Sep 17 00:00:00 2001 From: Sean Gillies Date: Thu, 23 Feb 2017 11:31:23 +0100 Subject: [PATCH 01/30] Start of tokens API support --- mapbox/services/tokens.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 mapbox/services/tokens.py diff --git a/mapbox/services/tokens.py b/mapbox/services/tokens.py new file mode 100644 index 0000000..81427ed --- /dev/null +++ b/mapbox/services/tokens.py @@ -0,0 +1,30 @@ +import arrow + +from uritemplate import URITemplate + +from mapbox.services.base import Service + + +class Tokens(Service): + """Access to the Tokens API.""" + + @property + def baseuri(self): + return 'https://{0}/tokens/v2'.format(self.host) + + def create(self, username, scopes=None, note=None, expires=0): + if not scopes: + raise ValueError("One or more token scopes are required") + if not note: + note = "SDK generated note" + + uri = URITemplate(self.baseuri + '/{username}').expand(username=username) + + payload = {'scopes': scopes, 'note': note} + + if expires > 0: + payload['expires'] = arrow.now().replace(seconds=+expires).isoformat() + + res = self.session.post(uri, json=payload) + self.handle_http_error(res) + return res From 44c0a16963eb6972255381b29c4691c9c75e65e1 Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Fri, 7 Jul 2017 10:56:13 +0530 Subject: [PATCH 02/30] Add `list_tokens` method to list tokens of an account --- mapbox/services/tokens.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mapbox/services/tokens.py b/mapbox/services/tokens.py index 81427ed..239142e 100644 --- a/mapbox/services/tokens.py +++ b/mapbox/services/tokens.py @@ -28,3 +28,14 @@ def create(self, username, scopes=None, note=None, expires=0): res = self.session.post(uri, json=payload) self.handle_http_error(res) return res + + def list_tokens(self, username, limit=None): + uri = URITemplate(self.baseuri + '/{username}').expand(username=username) + + params = {} + if limit: + params['limit'] = int(limit) + + res = self.session.get(uri, params=params) + self.handle_http_error(res) + return res From ed271d831ad9f8f7887739745e14a24839ea7b20 Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Fri, 7 Jul 2017 12:51:39 +0530 Subject: [PATCH 03/30] Add tests for token create and list --- mapbox/__init__.py | 1 + tests/test_tokens.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 tests/test_tokens.py diff --git a/mapbox/__init__.py b/mapbox/__init__.py index e73f3ff..1d86d23 100644 --- a/mapbox/__init__.py +++ b/mapbox/__init__.py @@ -8,4 +8,5 @@ from .services.mapmatching import MapMatcher from .services.surface import Surface from .services.static import Static +from .services.tokens import Tokens from .services.uploads import Uploader diff --git a/tests/test_tokens.py b/tests/test_tokens.py new file mode 100644 index 0000000..9c24b4b --- /dev/null +++ b/tests/test_tokens.py @@ -0,0 +1,32 @@ +import responses + +from mapbox.services.tokens import Tokens + + +@responses.activate +def test_token_create(): + """Token creation works""" + responses.add( + responses.POST, + 'https://api.mapbox.com/tokens/v2/testuser?access_token=pk.test', + match_querystring=True, + body='{"scopes": ["styles:read", "fonts:read"]}', + status=200, + content_type='application/json') + + response = Tokens(access_token='pk.test').create('testuser', ["styles:read", "fonts:read"]) + assert response.status_code == 200 + + +@responses.activate +def test_token_list(): + """Token listing works""" + responses.add( + responses.GET, + 'https://api.mapbox.com/tokens/v2/testuser?access_token=pk.test', + match_querystring=True, + status=200, + content_type='application/json') + + response = Tokens(access_token='pk.test').list_tokens('testuser') + assert response.status_code == 200 From fdc3425d1e5ba229b78afc04f67c473be534eb5c Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Sun, 9 Jul 2017 01:44:49 +0530 Subject: [PATCH 04/30] Add `create_temp_token` to create temporary token --- mapbox/services/tokens.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/mapbox/services/tokens.py b/mapbox/services/tokens.py index 239142e..f68c74b 100644 --- a/mapbox/services/tokens.py +++ b/mapbox/services/tokens.py @@ -39,3 +39,20 @@ def list_tokens(self, username, limit=None): res = self.session.get(uri, params=params) self.handle_http_error(res) return res + + def create_temp_token(self, username, scopes=None, expires=0): + if not scopes: + raise ValueError("One or more token scopes are required") + + uri = URITemplate(self.baseuri + '/{username}').expand(username=username) + + payload = {'scopes': scopes} + + if 0 < expires <= 3600: + payload['expires'] = arrow.now().replace(seconds=+expires).isoformat() + else: + raise ValueError("Expiry should be within 1 hour from now") + + res = self.session.post(uri, json=payload) + self.handle_http_error(res) + return res From 5aa9707de5b672cc76c6cedb4e9903401c33ac87 Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Sun, 9 Jul 2017 01:46:14 +0530 Subject: [PATCH 05/30] Add test for temporary token creation --- tests/test_tokens.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_tokens.py b/tests/test_tokens.py index 9c24b4b..fa931c6 100644 --- a/tests/test_tokens.py +++ b/tests/test_tokens.py @@ -30,3 +30,18 @@ def test_token_list(): response = Tokens(access_token='pk.test').list_tokens('testuser') assert response.status_code == 200 + + +@responses.activate +def test_temp_token_create(): + """Temporary token creation works""" + responses.add( + responses.POST, + 'https://api.mapbox.com/tokens/v2/testuser?access_token=sk.test', + match_querystring=True, + body='{"scopes": ["styles:read", "fonts:read"]}', + status=200, + content_type='application/json') + + response = Tokens(access_token='sk.test').create_temp_token('testuser', ["styles:read", "fonts:read"], 3600) + assert response.status_code == 200 From bdebf610fe038688a795458dc8909d3f3b8c0d27 Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Sun, 9 Jul 2017 02:25:01 +0530 Subject: [PATCH 06/30] Add `arrow` to dependencies --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 0247e92..192f0e2 100644 --- a/setup.py +++ b/setup.py @@ -30,6 +30,7 @@ include_package_data=True, zip_safe=False, install_requires=[ + 'arrow', 'boto3>=1.4', 'cachecontrol', 'click', From 07447eeb0ef8ad61c87f0621e608311bf324d297 Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Tue, 11 Jul 2017 09:53:39 +0530 Subject: [PATCH 07/30] Remove `arrow` dependency and use `datetime` and `timedelta` --- mapbox/services/tokens.py | 6 +++--- setup.py | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/mapbox/services/tokens.py b/mapbox/services/tokens.py index f68c74b..3307ffe 100644 --- a/mapbox/services/tokens.py +++ b/mapbox/services/tokens.py @@ -1,4 +1,4 @@ -import arrow +from datetime import datetime, timedelta from uritemplate import URITemplate @@ -23,7 +23,7 @@ def create(self, username, scopes=None, note=None, expires=0): payload = {'scopes': scopes, 'note': note} if expires > 0: - payload['expires'] = arrow.now().replace(seconds=+expires).isoformat() + payload['expires'] = (datetime.now() + timedelta(seconds=expires)).isoformat() res = self.session.post(uri, json=payload) self.handle_http_error(res) @@ -49,7 +49,7 @@ def create_temp_token(self, username, scopes=None, expires=0): payload = {'scopes': scopes} if 0 < expires <= 3600: - payload['expires'] = arrow.now().replace(seconds=+expires).isoformat() + payload['expires'] = (datetime.now() + timedelta(seconds=expires)).isoformat() else: raise ValueError("Expiry should be within 1 hour from now") diff --git a/setup.py b/setup.py index 192f0e2..0247e92 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,6 @@ include_package_data=True, zip_safe=False, install_requires=[ - 'arrow', 'boto3>=1.4', 'cachecontrol', 'click', From 14367af743df09276a2ec67a8e8988ac6d25b73b Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Tue, 11 Jul 2017 09:55:06 +0530 Subject: [PATCH 08/30] Add `update_auth` method to update scopes or note of a token --- mapbox/services/tokens.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/mapbox/services/tokens.py b/mapbox/services/tokens.py index 3307ffe..097b42e 100644 --- a/mapbox/services/tokens.py +++ b/mapbox/services/tokens.py @@ -56,3 +56,19 @@ def create_temp_token(self, username, scopes=None, expires=0): res = self.session.post(uri, json=payload) self.handle_http_error(res) return res + + def update_auth(self, username, authorization_id, scopes=None, note=None): + if not scopes and not note: + raise ValueError("Provide either scopes or a note to update token") + + uri = URITemplate(self.baseuri + '/{username}/{authorization_id}').expand(username=username, authorization_id=authorization_id) + + payload = {} + if scopes: + payload['scopes'] = scopes + if note: + payload['note'] = note + + res = self.session.patch(uri, json=payload) + self.handle_http_error(res) + return res From 5ead7784f292092b427a6379f94c9e43de7a5963 Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Tue, 11 Jul 2017 09:57:20 +0530 Subject: [PATCH 09/30] Add test for token authorization update --- tests/test_tokens.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_tokens.py b/tests/test_tokens.py index fa931c6..92c1da5 100644 --- a/tests/test_tokens.py +++ b/tests/test_tokens.py @@ -45,3 +45,18 @@ def test_temp_token_create(): response = Tokens(access_token='sk.test').create_temp_token('testuser', ["styles:read", "fonts:read"], 3600) assert response.status_code == 200 + + +@responses.activate +def test_update_token(): + """Token updation works""" + responses.add( + responses.PATCH, + 'https://api.mapbox.com/tokens/v2/testuser/auth_id?access_token=pk.test', + match_querystring=True, + body='{"scopes": ["styles:read", "fonts:read"]}', + status=200, + content_type='application/json') + + response = Tokens(access_token='pk.test').update_auth('testuser', 'auth_id', ["styles:read", "fonts:read"]) + assert response.status_code == 200 From 023cba7c2d4af421ccc077209611fb5b1325eb53 Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Tue, 11 Jul 2017 10:30:53 +0530 Subject: [PATCH 10/30] Add `delete_auth` method to delete authorization --- mapbox/services/tokens.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mapbox/services/tokens.py b/mapbox/services/tokens.py index 097b42e..38a9d01 100644 --- a/mapbox/services/tokens.py +++ b/mapbox/services/tokens.py @@ -72,3 +72,10 @@ def update_auth(self, username, authorization_id, scopes=None, note=None): res = self.session.patch(uri, json=payload) self.handle_http_error(res) return res + + def delete_auth(self, username, authorization_id): + uri = URITemplate(self.baseuri + '/{username}/{authorization_id}').expand(username=username, authorization_id=authorization_id) + + res = self.session.delete(uri) + self.handle_http_error(res) + return res From 874b31f8fb4fc90a37839fe7f49e3a3b2cd2c4ce Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Tue, 11 Jul 2017 10:32:11 +0530 Subject: [PATCH 11/30] Add test for authorization deletion --- tests/test_tokens.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_tokens.py b/tests/test_tokens.py index 92c1da5..ce81843 100644 --- a/tests/test_tokens.py +++ b/tests/test_tokens.py @@ -60,3 +60,16 @@ def test_update_token(): response = Tokens(access_token='pk.test').update_auth('testuser', 'auth_id', ["styles:read", "fonts:read"]) assert response.status_code == 200 + + +@responses.activate +def test_delete_auth(): + """Token authorization deletion works""" + responses.add( + responses.DELETE, + 'https://api.mapbox.com/tokens/v2/testuser/auth_id?access_token=pk.test', + match_querystring=True, + status=204) + + response = Tokens(access_token='pk.test').delete_auth('testuser', 'auth_id') + assert response.status_code == 204 From df071fe39ff803ae5eee4ff4e657cb1daf813319 Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Tue, 11 Jul 2017 10:51:55 +0530 Subject: [PATCH 12/30] Add `check_validity` method to retrieve a token, check validity --- mapbox/services/tokens.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mapbox/services/tokens.py b/mapbox/services/tokens.py index 38a9d01..d05e553 100644 --- a/mapbox/services/tokens.py +++ b/mapbox/services/tokens.py @@ -79,3 +79,10 @@ def delete_auth(self, username, authorization_id): res = self.session.delete(uri) self.handle_http_error(res) return res + + def check_validity(self): + uri = URITemplate(self.baseuri) + + res = self.session.get(uri) + self.handle_http_error(res) + return res From 6c226fc9b025d964449ab0c6d084d96f03794a64 Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Tue, 11 Jul 2017 10:53:15 +0530 Subject: [PATCH 13/30] Add test for retrieving validity information of a token --- tests/test_tokens.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_tokens.py b/tests/test_tokens.py index ce81843..78e595d 100644 --- a/tests/test_tokens.py +++ b/tests/test_tokens.py @@ -73,3 +73,17 @@ def test_delete_auth(): response = Tokens(access_token='pk.test').delete_auth('testuser', 'auth_id') assert response.status_code == 204 + + +@responses.activate +def test_check_validity(): + """Token checking validation works""" + responses.add( + responses.GET, + 'https://api.mapbox.com/tokens/v2?access_token=pk.test', + match_querystring=True, + status=200, + content_type='application/json') + + response = Tokens(access_token='pk.test').check_validity() + assert response.status_code == 200 From 10a109d1062a05f1c0cd0928c364221cf0ced718 Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Tue, 11 Jul 2017 19:23:26 +0530 Subject: [PATCH 14/30] Add `list_scopes` to list scopes for a token --- mapbox/services/tokens.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mapbox/services/tokens.py b/mapbox/services/tokens.py index d05e553..3f0614f 100644 --- a/mapbox/services/tokens.py +++ b/mapbox/services/tokens.py @@ -86,3 +86,10 @@ def check_validity(self): res = self.session.get(uri) self.handle_http_error(res) return res + + def list_scopes(self, username): + uri = URITemplate('https://{host}/scopes/v1' + '/{username}').expand(host=self.host, username=username) + + res = self.session.get(uri) + self.handle_http_error(res) + return res From 55fe480b6088904d7a521bea6f91b731a59476f2 Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Tue, 11 Jul 2017 19:23:52 +0530 Subject: [PATCH 15/30] Add test for listing scopes of a token --- tests/test_tokens.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_tokens.py b/tests/test_tokens.py index 78e595d..ed4c5ca 100644 --- a/tests/test_tokens.py +++ b/tests/test_tokens.py @@ -87,3 +87,17 @@ def test_check_validity(): response = Tokens(access_token='pk.test').check_validity() assert response.status_code == 200 + + +@responses.activate +def test_list_scopes(): + """Listing of scopes for a token works""" + responses.add( + responses.GET, + 'https://api.mapbox.com/scopes/v1/testuser?access_token=pk.test', + match_querystring=True, + status=200, + content_type='application/json') + + response = Tokens(access_token='pk.test').list_scopes('testuser') + assert response.status_code == 200 From e0dd5b15b92ec22df2245241e4bfba23afbe07f0 Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Wed, 12 Jul 2017 09:38:49 +0530 Subject: [PATCH 16/30] Remove `expires` arg to differentiate permanent and temporary token --- mapbox/services/tokens.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mapbox/services/tokens.py b/mapbox/services/tokens.py index 3f0614f..7cde57f 100644 --- a/mapbox/services/tokens.py +++ b/mapbox/services/tokens.py @@ -12,7 +12,7 @@ class Tokens(Service): def baseuri(self): return 'https://{0}/tokens/v2'.format(self.host) - def create(self, username, scopes=None, note=None, expires=0): + def create(self, username, scopes=None, note=None): if not scopes: raise ValueError("One or more token scopes are required") if not note: @@ -22,9 +22,6 @@ def create(self, username, scopes=None, note=None, expires=0): payload = {'scopes': scopes, 'note': note} - if expires > 0: - payload['expires'] = (datetime.now() + timedelta(seconds=expires)).isoformat() - res = self.session.post(uri, json=payload) self.handle_http_error(res) return res From 92639ce982ae4bf9dcb3064698276b60f1b29283 Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Wed, 12 Jul 2017 09:53:04 +0530 Subject: [PATCH 17/30] Change default `expires` to 3600 seconds(1 hour) --- mapbox/services/tokens.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mapbox/services/tokens.py b/mapbox/services/tokens.py index 7cde57f..7566e84 100644 --- a/mapbox/services/tokens.py +++ b/mapbox/services/tokens.py @@ -37,7 +37,7 @@ def list_tokens(self, username, limit=None): self.handle_http_error(res) return res - def create_temp_token(self, username, scopes=None, expires=0): + def create_temp_token(self, username, scopes=None, expires=3600): if not scopes: raise ValueError("One or more token scopes are required") From 6ccab3c2b943e164e06673c76f4f32a3d35c06a4 Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Wed, 12 Jul 2017 09:55:19 +0530 Subject: [PATCH 18/30] Change `datetime.now()` to `datetime.utcnow()` --- mapbox/services/tokens.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mapbox/services/tokens.py b/mapbox/services/tokens.py index 7566e84..99e8d3a 100644 --- a/mapbox/services/tokens.py +++ b/mapbox/services/tokens.py @@ -46,7 +46,7 @@ def create_temp_token(self, username, scopes=None, expires=3600): payload = {'scopes': scopes} if 0 < expires <= 3600: - payload['expires'] = (datetime.now() + timedelta(seconds=expires)).isoformat() + payload['expires'] = (datetime.utcnow() + timedelta(seconds=expires)).isoformat() else: raise ValueError("Expiry should be within 1 hour from now") From 73a38a5da1ac2cd6de82b420f44b6cdeedeca1f6 Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Wed, 12 Jul 2017 09:58:12 +0530 Subject: [PATCH 19/30] Raise `ValidationError` instead of a `ValueError` --- mapbox/services/tokens.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mapbox/services/tokens.py b/mapbox/services/tokens.py index 99e8d3a..0a7bebf 100644 --- a/mapbox/services/tokens.py +++ b/mapbox/services/tokens.py @@ -2,6 +2,7 @@ from uritemplate import URITemplate +from mapbox.errors import ValidationError from mapbox.services.base import Service @@ -45,10 +46,9 @@ def create_temp_token(self, username, scopes=None, expires=3600): payload = {'scopes': scopes} - if 0 < expires <= 3600: - payload['expires'] = (datetime.utcnow() + timedelta(seconds=expires)).isoformat() - else: - raise ValueError("Expiry should be within 1 hour from now") + if expires <= 0 or expires > 3600: + raise ValidationError("Expiry should be within 1 hour from now") + payload['expires'] = (datetime.utcnow() + timedelta(seconds=expires)).isoformat() res = self.session.post(uri, json=payload) self.handle_http_error(res) From 2f276034965a2870a87ef63de59957faa3892aec Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Wed, 12 Jul 2017 10:17:47 +0530 Subject: [PATCH 20/30] Use default username from base and make `username` as kwarg --- mapbox/services/tokens.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/mapbox/services/tokens.py b/mapbox/services/tokens.py index 0a7bebf..12719d9 100644 --- a/mapbox/services/tokens.py +++ b/mapbox/services/tokens.py @@ -13,7 +13,9 @@ class Tokens(Service): def baseuri(self): return 'https://{0}/tokens/v2'.format(self.host) - def create(self, username, scopes=None, note=None): + def create(self, username=None, scopes=None, note=None): + if username is None: + username = self.username if not scopes: raise ValueError("One or more token scopes are required") if not note: @@ -27,7 +29,9 @@ def create(self, username, scopes=None, note=None): self.handle_http_error(res) return res - def list_tokens(self, username, limit=None): + def list_tokens(self, username=None, limit=None): + if username is None: + username = self.username uri = URITemplate(self.baseuri + '/{username}').expand(username=username) params = {} @@ -38,7 +42,9 @@ def list_tokens(self, username, limit=None): self.handle_http_error(res) return res - def create_temp_token(self, username, scopes=None, expires=3600): + def create_temp_token(self, username=None, scopes=None, expires=3600): + if username is None: + username = self.username if not scopes: raise ValueError("One or more token scopes are required") @@ -54,7 +60,9 @@ def create_temp_token(self, username, scopes=None, expires=3600): self.handle_http_error(res) return res - def update_auth(self, username, authorization_id, scopes=None, note=None): + def update_auth(self, authorization_id, username=None, scopes=None, note=None): + if username is None: + username = self.username if not scopes and not note: raise ValueError("Provide either scopes or a note to update token") @@ -70,7 +78,9 @@ def update_auth(self, username, authorization_id, scopes=None, note=None): self.handle_http_error(res) return res - def delete_auth(self, username, authorization_id): + def delete_auth(self, authorization_id, username=None): + if username is None: + username = self.username uri = URITemplate(self.baseuri + '/{username}/{authorization_id}').expand(username=username, authorization_id=authorization_id) res = self.session.delete(uri) @@ -84,7 +94,9 @@ def check_validity(self): self.handle_http_error(res) return res - def list_scopes(self, username): + def list_scopes(self, username=None): + if username is None: + username = self.username uri = URITemplate('https://{host}/scopes/v1' + '/{username}').expand(host=self.host, username=username) res = self.session.get(uri) From f4fd83356fc797ed38457d8ebe4c2fc5dda297d9 Mon Sep 17 00:00:00 2001 From: Mrinal Purohit Date: Wed, 12 Jul 2017 10:24:04 +0530 Subject: [PATCH 21/30] Update tests after `username` kwarg change, shifting of arguments --- tests/test_tokens.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_tokens.py b/tests/test_tokens.py index ed4c5ca..6b57759 100644 --- a/tests/test_tokens.py +++ b/tests/test_tokens.py @@ -43,7 +43,7 @@ def test_temp_token_create(): status=200, content_type='application/json') - response = Tokens(access_token='sk.test').create_temp_token('testuser', ["styles:read", "fonts:read"], 3600) + response = Tokens(access_token='sk.test').create_temp_token('testuser', ["styles:read", "fonts:read"]) assert response.status_code == 200 @@ -58,7 +58,7 @@ def test_update_token(): status=200, content_type='application/json') - response = Tokens(access_token='pk.test').update_auth('testuser', 'auth_id', ["styles:read", "fonts:read"]) + response = Tokens(access_token='pk.test').update_auth('auth_id', 'testuser', ["styles:read", "fonts:read"]) assert response.status_code == 200 @@ -71,7 +71,7 @@ def test_delete_auth(): match_querystring=True, status=204) - response = Tokens(access_token='pk.test').delete_auth('testuser', 'auth_id') + response = Tokens(access_token='pk.test').delete_auth('auth_id', 'testuser') assert response.status_code == 204 From 3d459fad78e5d18013798b1595a2f23e8f32c928 Mon Sep 17 00:00:00 2001 From: Matthew Perry Date: Wed, 12 Jul 2017 10:37:12 -0600 Subject: [PATCH 22/30] break uri lines to <80 char --- mapbox/services/tokens.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/mapbox/services/tokens.py b/mapbox/services/tokens.py index 12719d9..9a0ef57 100644 --- a/mapbox/services/tokens.py +++ b/mapbox/services/tokens.py @@ -21,7 +21,8 @@ def create(self, username=None, scopes=None, note=None): if not note: note = "SDK generated note" - uri = URITemplate(self.baseuri + '/{username}').expand(username=username) + uri = URITemplate( + self.baseuri + '/{username}').expand(username=username) payload = {'scopes': scopes, 'note': note} @@ -32,7 +33,9 @@ def create(self, username=None, scopes=None, note=None): def list_tokens(self, username=None, limit=None): if username is None: username = self.username - uri = URITemplate(self.baseuri + '/{username}').expand(username=username) + + uri = URITemplate( + self.baseuri + '/{username}').expand(username=username) params = {} if limit: @@ -48,7 +51,8 @@ def create_temp_token(self, username=None, scopes=None, expires=3600): if not scopes: raise ValueError("One or more token scopes are required") - uri = URITemplate(self.baseuri + '/{username}').expand(username=username) + uri = URITemplate( + self.baseuri + '/{username}').expand(username=username) payload = {'scopes': scopes} @@ -66,7 +70,9 @@ def update_auth(self, authorization_id, username=None, scopes=None, note=None): if not scopes and not note: raise ValueError("Provide either scopes or a note to update token") - uri = URITemplate(self.baseuri + '/{username}/{authorization_id}').expand(username=username, authorization_id=authorization_id) + uri = URITemplate( + self.baseuri + '/{username}/{authorization_id}').expand( + username=username, authorization_id=authorization_id) payload = {} if scopes: @@ -81,7 +87,10 @@ def update_auth(self, authorization_id, username=None, scopes=None, note=None): def delete_auth(self, authorization_id, username=None): if username is None: username = self.username - uri = URITemplate(self.baseuri + '/{username}/{authorization_id}').expand(username=username, authorization_id=authorization_id) + + uri = URITemplate( + self.baseuri + '/{username}/{authorization_id}').expand( + username=username, authorization_id=authorization_id) res = self.session.delete(uri) self.handle_http_error(res) @@ -97,7 +106,9 @@ def check_validity(self): def list_scopes(self, username=None): if username is None: username = self.username - uri = URITemplate('https://{host}/scopes/v1' + '/{username}').expand(host=self.host, username=username) + uri = URITemplate( + 'https://{host}/scopes/v1' + '/{username}').expand( + host=self.host, username=username) res = self.session.get(uri) self.handle_http_error(res) From fe8f66f7578e3bdc2884232ecf2f465b1a0145cf Mon Sep 17 00:00:00 2001 From: Matthew Perry Date: Wed, 12 Jul 2017 11:07:22 -0600 Subject: [PATCH 23/30] remove _auth from methods --- mapbox/services/tokens.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mapbox/services/tokens.py b/mapbox/services/tokens.py index 9a0ef57..f08ab47 100644 --- a/mapbox/services/tokens.py +++ b/mapbox/services/tokens.py @@ -64,7 +64,7 @@ def create_temp_token(self, username=None, scopes=None, expires=3600): self.handle_http_error(res) return res - def update_auth(self, authorization_id, username=None, scopes=None, note=None): + def update(self, authorization_id, username=None, scopes=None, note=None): if username is None: username = self.username if not scopes and not note: @@ -84,7 +84,7 @@ def update_auth(self, authorization_id, username=None, scopes=None, note=None): self.handle_http_error(res) return res - def delete_auth(self, authorization_id, username=None): + def delete(self, authorization_id, username=None): if username is None: username = self.username @@ -106,8 +106,9 @@ def check_validity(self): def list_scopes(self, username=None): if username is None: username = self.username + uri = URITemplate( - 'https://{host}/scopes/v1' + '/{username}').expand( + 'https://{host}/scopes/v1/{username}').expand( host=self.host, username=username) res = self.session.get(uri) From 70b564d2d4cba03e3205c95823a82c93cef2d1e2 Mon Sep 17 00:00:00 2001 From: Matthew Perry Date: Wed, 12 Jul 2017 11:07:42 -0600 Subject: [PATCH 24/30] initial documentation and doctests --- docs/tokens.md | 126 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 docs/tokens.md diff --git a/docs/tokens.md b/docs/tokens.md new file mode 100644 index 0000000..bff81d3 --- /dev/null +++ b/docs/tokens.md @@ -0,0 +1,126 @@ +# Tokens + +The `Tokens` class (`from mapbox import Tokens`) provides +access to the Mapbox Tokens API, allowing you to programmaticaly create +Mapbox access tokens to access Mapbox resources on behalf of a user. + +```python + +>>> from mapbox import Tokens +>>> service = Tokens() + +``` + +See https://www.mapbox.com/api-documentation/#tokens for general documentation of the API. + +This API requires an **initial token** with the `tokens:write` scope. +Your Mapbox access token should be set in your environment; +see the [access tokens](access_tokens.md) documentation for more information. + +The Mapbox username associated with each account is determined by the access_token by default. All of the methods also take an optional `username` keyword argument to override this default. + +## List tokens + +```python + +>>> response = service.list_tokens() +>>> response.json() +[...] + +``` + +## Create temporary tokens + +Generate a token for temporary access to mapbox APIs using the +`create_temp_token` method. Tokens can bet set to expire at any time up to one hour. + +```python + +>>> response = service.create_temp_token( +... scopes=['styles:read'], +... expires=60) # seconds +>>> auth = response.json() +>>> auth['token'][:3] +'tk.' + +``` + + +## Create a permanet token + + +```python + +>>> response = service.create( +... scopes=['styles:read'], +... note='test-token') +>>> auth = response.json() +>>> auth['scopes'] +['styles:read'] +>>> auth['token'][:3] +'pk.' + +``` + +If you create a token with public/read scopes, your token with be a public token, starting with `pk`. If the token has secret/write scopes, the token will be secret, starting with `sk`. + +If you want to create a token that may contain secret/write scopes, you must create the token with at least one such scope initially. + +## Update a token + +To update the scopes of a token + +```python + +>>> response = service.update( +... authorization_id=auth['id'], +... scopes=['styles:read', 'datasets:read'], +... note="updated") +>>> auth = response.json() +>>> assert response.status_code == 200 + + +``` + +## Check validity of a token + +```python + +>>> service.check_validity().json()['code'] +'TokenValid' + +``` + +Note that this applies only to the access token which is making the request. +If you want to check the validity of other tokens, you must make a separate instance of the `Tokens` service class using the desired `access_token`. + +```python + +>>> new_service = Tokens(access_token=auth['token']) + +``` + +## List the scopes of a token + +```python + +>>> response = service.list_scopes() +>>> response.json() +[...] + +``` + +As with checking validity, this method applies only to the access token which is making the request. + + +## Delete a token + +```python + +>>> response = service.delete( +... authorization_id=auth['id']) +>>> assert response.status_code == 204 + +``` + + From cd2a5931908f684d9c645d0566b5f2848eb9a8a6 Mon Sep 17 00:00:00 2001 From: Matthew Perry Date: Wed, 12 Jul 2017 11:15:06 -0600 Subject: [PATCH 25/30] update unit tests --- tests/test_tokens.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/test_tokens.py b/tests/test_tokens.py index 6b57759..753b549 100644 --- a/tests/test_tokens.py +++ b/tests/test_tokens.py @@ -14,7 +14,8 @@ def test_token_create(): status=200, content_type='application/json') - response = Tokens(access_token='pk.test').create('testuser', ["styles:read", "fonts:read"]) + response = Tokens(access_token='pk.test').create( + 'testuser', ["styles:read", "fonts:read"]) assert response.status_code == 200 @@ -43,7 +44,8 @@ def test_temp_token_create(): status=200, content_type='application/json') - response = Tokens(access_token='sk.test').create_temp_token('testuser', ["styles:read", "fonts:read"]) + response = Tokens(access_token='sk.test').create_temp_token( + 'testuser', ["styles:read", "fonts:read"]) assert response.status_code == 200 @@ -58,12 +60,13 @@ def test_update_token(): status=200, content_type='application/json') - response = Tokens(access_token='pk.test').update_auth('auth_id', 'testuser', ["styles:read", "fonts:read"]) + response = Tokens(access_token='pk.test').update( + 'auth_id', 'testuser', ["styles:read", "fonts:read"]) assert response.status_code == 200 @responses.activate -def test_delete_auth(): +def test_delete(): """Token authorization deletion works""" responses.add( responses.DELETE, @@ -71,7 +74,7 @@ def test_delete_auth(): match_querystring=True, status=204) - response = Tokens(access_token='pk.test').delete_auth('auth_id', 'testuser') + response = Tokens(access_token='pk.test').delete('auth_id', 'testuser') assert response.status_code == 204 From 487be09b4fefa729f89da3b18540a253e39cab2c Mon Sep 17 00:00:00 2001 From: Matthew Perry Date: Wed, 12 Jul 2017 11:20:45 -0600 Subject: [PATCH 26/30] make scopes a required arg --- mapbox/services/tokens.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mapbox/services/tokens.py b/mapbox/services/tokens.py index f08ab47..b50b78d 100644 --- a/mapbox/services/tokens.py +++ b/mapbox/services/tokens.py @@ -45,11 +45,9 @@ def list_tokens(self, username=None, limit=None): self.handle_http_error(res) return res - def create_temp_token(self, username=None, scopes=None, expires=3600): + def create_temp_token(self, scopes, username=None, expires=3600): if username is None: username = self.username - if not scopes: - raise ValueError("One or more token scopes are required") uri = URITemplate( self.baseuri + '/{username}').expand(username=username) From 25a8be73b90d39f252162ef9b2970190ea3e350f Mon Sep 17 00:00:00 2001 From: Matthew Perry Date: Wed, 12 Jul 2017 12:05:16 -0600 Subject: [PATCH 27/30] function signatures, username kwarg at the end --- mapbox/services/tokens.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/mapbox/services/tokens.py b/mapbox/services/tokens.py index b50b78d..b7ec98e 100644 --- a/mapbox/services/tokens.py +++ b/mapbox/services/tokens.py @@ -13,11 +13,9 @@ class Tokens(Service): def baseuri(self): return 'https://{0}/tokens/v2'.format(self.host) - def create(self, username=None, scopes=None, note=None): + def create(self, scopes, note=None, username=None): if username is None: username = self.username - if not scopes: - raise ValueError("One or more token scopes are required") if not note: note = "SDK generated note" @@ -30,7 +28,7 @@ def create(self, username=None, scopes=None, note=None): self.handle_http_error(res) return res - def list_tokens(self, username=None, limit=None): + def list_tokens(self, limit=None, username=None): if username is None: username = self.username @@ -45,7 +43,7 @@ def list_tokens(self, username=None, limit=None): self.handle_http_error(res) return res - def create_temp_token(self, scopes, username=None, expires=3600): + def create_temp_token(self, scopes, expires=3600, username=None): if username is None: username = self.username @@ -62,11 +60,11 @@ def create_temp_token(self, scopes, username=None, expires=3600): self.handle_http_error(res) return res - def update(self, authorization_id, username=None, scopes=None, note=None): + def update(self, authorization_id, scopes=None, note=None, username=None): if username is None: username = self.username if not scopes and not note: - raise ValueError("Provide either scopes or a note to update token") + raise ValidationError("Provide either scopes or a note to update token") uri = URITemplate( self.baseuri + '/{username}/{authorization_id}').expand( From 9e1c65c0500952ae40184e0b93592d47d05f6c7b Mon Sep 17 00:00:00 2001 From: Matthew Perry Date: Wed, 12 Jul 2017 12:05:29 -0600 Subject: [PATCH 28/30] unit tests to 100 percent coverage --- tests/test_tokens.py | 154 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 137 insertions(+), 17 deletions(-) diff --git a/tests/test_tokens.py b/tests/test_tokens.py index 753b549..c011223 100644 --- a/tests/test_tokens.py +++ b/tests/test_tokens.py @@ -1,6 +1,14 @@ import responses +import base64 + +import pytest from mapbox.services.tokens import Tokens +from mapbox.errors import ValidationError + + +token = 'sk.{0}.test'.format( + base64.b64encode(b'{"u":"testuser"}').decode('utf-8')) @responses.activate @@ -8,14 +16,29 @@ def test_token_create(): """Token creation works""" responses.add( responses.POST, - 'https://api.mapbox.com/tokens/v2/testuser?access_token=pk.test', + 'https://api.mapbox.com/tokens/v2/testuser?access_token={0}'.format(token), match_querystring=True, body='{"scopes": ["styles:read", "fonts:read"]}', status=200, content_type='application/json') - response = Tokens(access_token='pk.test').create( - 'testuser', ["styles:read", "fonts:read"]) + response = Tokens(access_token=token).create( + ["styles:read", "fonts:read"], note="new token") + assert response.status_code == 200 + + +@responses.activate +def test_token_create_username(): + responses.add( + responses.POST, + 'https://api.mapbox.com/tokens/v2/testuser?access_token={0}'.format(token), + match_querystring=True, + body='{"scopes": ["styles:read", "fonts:read"]}', + status=200, + content_type='application/json') + + response = Tokens(access_token=token).create( + ["styles:read", "fonts:read"], username='testuser') assert response.status_code == 200 @@ -24,12 +47,38 @@ def test_token_list(): """Token listing works""" responses.add( responses.GET, - 'https://api.mapbox.com/tokens/v2/testuser?access_token=pk.test', + 'https://api.mapbox.com/tokens/v2/testuser?access_token={0}'.format(token), match_querystring=True, status=200, content_type='application/json') - response = Tokens(access_token='pk.test').list_tokens('testuser') + response = Tokens(access_token=token).list_tokens() + assert response.status_code == 200 + + +@responses.activate +def test_token_list_username(): + responses.add( + responses.GET, + 'https://api.mapbox.com/tokens/v2/testuser?access_token={0}'.format(token), + match_querystring=True, + status=200, + content_type='application/json') + + response = Tokens(access_token=token).list_tokens(username='testuser') + assert response.status_code == 200 + + +@responses.activate +def test_token_list_limit(): + responses.add( + responses.GET, + 'https://api.mapbox.com/tokens/v2/testuser?access_token={0}&limit=5'.format(token), + match_querystring=True, + status=200, + content_type='application/json') + + response = Tokens(access_token=token).list_tokens(limit=5) assert response.status_code == 200 @@ -38,43 +87,101 @@ def test_temp_token_create(): """Temporary token creation works""" responses.add( responses.POST, - 'https://api.mapbox.com/tokens/v2/testuser?access_token=sk.test', + 'https://api.mapbox.com/tokens/v2/testuser?access_token={0}'.format(token), match_querystring=True, body='{"scopes": ["styles:read", "fonts:read"]}', status=200, content_type='application/json') - response = Tokens(access_token='sk.test').create_temp_token( - 'testuser', ["styles:read", "fonts:read"]) + response = Tokens(access_token=token).create_temp_token( + ["styles:read", "fonts:read"]) assert response.status_code == 200 +@responses.activate +def test_temp_token_create_username(): + """Temporary token creation works""" + responses.add( + responses.POST, + 'https://api.mapbox.com/tokens/v2/testuser?access_token={0}'.format(token), + match_querystring=True, + body='{"scopes": ["styles:read", "fonts:read"]}', + status=200, + content_type='application/json') + + response = Tokens(access_token=token).create_temp_token( + ["styles:read", "fonts:read"], username='testuser') + assert response.status_code == 200 + + +def test_temp_token_expire(): + with pytest.raises(ValidationError): + response = Tokens(access_token=token).create_temp_token( + ["styles:read"], expires=3601) + + @responses.activate def test_update_token(): """Token updation works""" responses.add( responses.PATCH, - 'https://api.mapbox.com/tokens/v2/testuser/auth_id?access_token=pk.test', + 'https://api.mapbox.com/tokens/v2/testuser/auth_id?access_token={0}'.format(token), + match_querystring=True, + body='{"scopes": ["styles:read", "fonts:read"]}', + status=200, + content_type='application/json') + + response = Tokens(access_token=token).update( + 'auth_id', note="updated token") + assert response.status_code == 200 + + +@responses.activate +def test_update_token_username(): + """Token updation works""" + responses.add( + responses.PATCH, + 'https://api.mapbox.com/tokens/v2/testuser/auth_id?access_token={0}'.format(token), match_querystring=True, body='{"scopes": ["styles:read", "fonts:read"]}', status=200, content_type='application/json') - response = Tokens(access_token='pk.test').update( - 'auth_id', 'testuser', ["styles:read", "fonts:read"]) + response = Tokens(access_token=token).update( + 'auth_id', ["styles:read", "fonts:read"], + username='testuser') assert response.status_code == 200 +def test_temp_token_update_error(): + """update requires scope or notes""" + with pytest.raises(ValidationError): + Tokens(access_token=token).update('auth_id') + + @responses.activate def test_delete(): """Token authorization deletion works""" responses.add( responses.DELETE, - 'https://api.mapbox.com/tokens/v2/testuser/auth_id?access_token=pk.test', + 'https://api.mapbox.com/tokens/v2/testuser/auth_id?access_token={0}'.format(token), + match_querystring=True, + status=204) + + response = Tokens(access_token=token).delete('auth_id') + assert response.status_code == 204 + + +@responses.activate +def test_delete_username(): + responses.add( + responses.DELETE, + 'https://api.mapbox.com/tokens/v2/testuser/auth_id?access_token={0}'.format(token), match_querystring=True, status=204) - response = Tokens(access_token='pk.test').delete('auth_id', 'testuser') + response = Tokens(access_token=token).delete( + 'auth_id', username='testuser') assert response.status_code == 204 @@ -83,12 +190,12 @@ def test_check_validity(): """Token checking validation works""" responses.add( responses.GET, - 'https://api.mapbox.com/tokens/v2?access_token=pk.test', + 'https://api.mapbox.com/tokens/v2?access_token={0}'.format(token), match_querystring=True, status=200, content_type='application/json') - response = Tokens(access_token='pk.test').check_validity() + response = Tokens(access_token=token).check_validity() assert response.status_code == 200 @@ -97,10 +204,23 @@ def test_list_scopes(): """Listing of scopes for a token works""" responses.add( responses.GET, - 'https://api.mapbox.com/scopes/v1/testuser?access_token=pk.test', + 'https://api.mapbox.com/scopes/v1/testuser?access_token={0}'.format(token), + match_querystring=True, + status=200, + content_type='application/json') + + response = Tokens(access_token=token).list_scopes() + assert response.status_code == 200 + + +@responses.activate +def test_list_scopes_username(): + responses.add( + responses.GET, + 'https://api.mapbox.com/scopes/v1/testuser?access_token={0}'.format(token), match_querystring=True, status=200, content_type='application/json') - response = Tokens(access_token='pk.test').list_scopes('testuser') + response = Tokens(access_token=token).list_scopes(username='testuser') assert response.status_code == 200 From 544c7cc73ee56a1028e21ab35fc774722250ae91 Mon Sep 17 00:00:00 2001 From: Matthew Perry Date: Wed, 12 Jul 2017 12:22:52 -0600 Subject: [PATCH 29/30] docstrings --- mapbox/services/tokens.py | 84 +++++++++++++++++++++++++++++++++++++++ tests/test_tokens.py | 2 +- 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/mapbox/services/tokens.py b/mapbox/services/tokens.py index b7ec98e..0405807 100644 --- a/mapbox/services/tokens.py +++ b/mapbox/services/tokens.py @@ -14,6 +14,18 @@ def baseuri(self): return 'https://{0}/tokens/v2'.format(self.host) def create(self, scopes, note=None, username=None): + """Create a permanent token + + Parameters + ---------- + scopes: list + note: string + username: string, defaults to username in access token + + Returns + ------- + requests.Response + """ if username is None: username = self.username if not note: @@ -29,6 +41,17 @@ def create(self, scopes, note=None, username=None): return res def list_tokens(self, limit=None, username=None): + """List all permanent tokens + + Parameters + ---------- + limit: int + username: string, defaults to username in access token + + Returns + ------- + requests.Response + """ if username is None: username = self.username @@ -44,6 +67,21 @@ def list_tokens(self, limit=None, username=None): return res def create_temp_token(self, scopes, expires=3600, username=None): + """Create a temporary token + + Parameters + ---------- + scopes: list + List of valid mapbox token scope strings + expires: int + seconds, defaults to 3600 (1 hr) + username: string + defaults to username in access token + + Returns + ------- + requests.Response + """ if username is None: username = self.username @@ -61,6 +99,22 @@ def create_temp_token(self, scopes, expires=3600, username=None): return res def update(self, authorization_id, scopes=None, note=None, username=None): + """Update a token's scopes or note + + Parameters + ---------- + authorization_id: string + id of the token to update (not the token itself) + scopes: list + List of valid mapbox token scope strings + note: string + username: string + defaults to username in access token + + Returns + ------- + requests.Response + """ if username is None: username = self.username if not scopes and not note: @@ -81,6 +135,19 @@ def update(self, authorization_id, scopes=None, note=None, username=None): return res def delete(self, authorization_id, username=None): + """Delete a token + + Parameters + ---------- + authorization_id: string + id of the token to update (not the token itself) + username: string + defaults to username in access token + + Returns + ------- + requests.Response + """ if username is None: username = self.username @@ -93,6 +160,12 @@ def delete(self, authorization_id, username=None): return res def check_validity(self): + """Check validity of the token + + Returns + ------- + requests.Response + """ uri = URITemplate(self.baseuri) res = self.session.get(uri) @@ -100,6 +173,17 @@ def check_validity(self): return res def list_scopes(self, username=None): + """Delete a token + + Parameters + ---------- + username: string + defaults to username in access token + + Returns + ------- + requests.Response + """ if username is None: username = self.username diff --git a/tests/test_tokens.py b/tests/test_tokens.py index c011223..96605c5 100644 --- a/tests/test_tokens.py +++ b/tests/test_tokens.py @@ -116,7 +116,7 @@ def test_temp_token_create_username(): def test_temp_token_expire(): with pytest.raises(ValidationError): - response = Tokens(access_token=token).create_temp_token( + Tokens(access_token=token).create_temp_token( ["styles:read"], expires=3601) From 6cc333d124c3e7241ff5731267def6fddf2482f4 Mon Sep 17 00:00:00 2001 From: Matthew Perry Date: Wed, 12 Jul 2017 15:28:18 -0600 Subject: [PATCH 30/30] typo --- docs/tokens.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tokens.md b/docs/tokens.md index bff81d3..89bf273 100644 --- a/docs/tokens.md +++ b/docs/tokens.md @@ -46,7 +46,7 @@ Generate a token for temporary access to mapbox APIs using the ``` -## Create a permanet token +## Create a permanent token ```python