Repository: mapbox/mapbox-sdk-py

Issue:
Get user from token
As in https://github.com/mapbox/mapbox-sdk-js/blob/master/lib/get_user.js. This allows us to eliminate a duplicate parameter when creating dataset and upload service instances.

Discussion:

Patch:
diff --git a/mapbox/services/base.py b/mapbox/services/base.py
index 4a25402..efd0eeb 100644
--- a/mapbox/services/base.py
+++ b/mapbox/services/base.py
@@ -1,6 +1,8 @@
 """Base Service class"""
 
 import os
+import base64
+import json
 
 import requests
 
@@ -27,6 +29,20 @@ class Service:
         """A product token for use in User-Agent headers."""
         return 'mapbox-sdk-py/{0}'.format(__version__)
 
+    @property
+    def username(self):
+        """Get username from access token
+        Token contains base64 encoded json object with username"""
+        token = self.session.params['access_token']
+        if not token:
+            raise ValueError("session does not have a valid access_token param")
+        data = token.split('.')[1]
+        data = data.replace('-', '+').replace('_', '/')
+        try:
+            return json.loads(base64.b64decode(data).decode('utf-8'))['u']
+        except (ValueError, KeyError):
+            raise ValueError("access_token does not contain username")
+
     def handle_http_error(self, response, custom_messages=None,
                           raise_for_status=False):
         if not custom_messages:
diff --git a/mapbox/services/uploads.py b/mapbox/services/uploads.py
index 47e2451..635a53e 100644
--- a/mapbox/services/uploads.py
+++ b/mapbox/services/uploads.py
@@ -11,7 +11,7 @@ class Uploader(Service):
 
         from mapbox import Uploader
 
-        u = Uploader('username')
+        u = Uploader()
         url = u.stage('test.tif')
         job = u.create(url, 'test1').json()
 
@@ -24,8 +24,7 @@ class Uploader(Service):
         assert job not in u.list().json()
     """
 
-    def __init__(self, username, access_token=None):
-        self.username = username
+    def __init__(self, access_token=None):
         self.baseuri = 'https://api.mapbox.com/uploads/v1'
         self.session = self.get_session(access_token)
 


Test patch:
diff --git a/tests/test_base.py b/tests/test_base.py
index ff773d7..39a3311 100644
--- a/tests/test_base.py
+++ b/tests/test_base.py
@@ -1,3 +1,6 @@
+import base64
+import os
+
 import pytest
 import requests
 import responses
@@ -60,3 +63,36 @@ def test_custom_messages():
     with pytest.raises(requests.exceptions.HTTPError) as exc:
         assert service.handle_http_error(response, raise_for_status=True)
         assert "401" in exc.value.message
+
+
+class MockService(mapbox.Service):
+    def __init__(self, access_token=None):
+        # In order to get a username, a session must be created on init
+        self.session = self.get_session(access_token)
+
+def test_username(monkeypatch):
+    token = 'pk.{0}.test'.format(base64.b64encode(b'{"u":"testuser"}').decode('utf-8'))
+    service = MockService(access_token=token)
+    assert service.username == 'testuser'
+
+def test_username_failures(monkeypatch):
+    # If your child class doesn't create a session
+    service = mapbox.Service()
+    with pytest.raises(AttributeError) as exc:
+        service.username
+        assert 'session' in exc.value.message
+
+    if 'MAPBOX_ACCESS_TOKEN' in os.environ:
+        monkeypatch.delenv('MAPBOX_ACCESS_TOKEN')
+    service = MockService()
+    with pytest.raises(ValueError) as exc:
+        service.username
+        assert 'access_token' in exc.value.message
+        assert 'param' in exc.value.message
+
+    token = "not.good"
+    service = MockService(access_token=token)
+    with pytest.raises(ValueError) as exc:
+        service.username
+        assert 'access_token' in exc.value.message
+        assert 'username' in exc.value.message
diff --git a/tests/test_upload.py b/tests/test_upload.py
index 252c540..9d92810 100644
--- a/tests/test_upload.py
+++ b/tests/test_upload.py
@@ -1,4 +1,5 @@
 import json
+import base64
 
 import responses
 
@@ -6,6 +7,9 @@ import mapbox
 
 
 username = 'testuser'
+access_token = 'pk.{0}.test'.format(
+    base64.b64encode(b'{"u":"testuser"}').decode('utf-8'))
+
 upload_response_body = """
     {{"progress": 0,
     "modified": "date.test",
@@ -30,12 +34,12 @@ def test_get_credentials():
 
     responses.add(
         responses.GET,
-        'https://api.mapbox.com/uploads/v1/{0}/credentials?access_token=pk.test'.format(username),
+        'https://api.mapbox.com/uploads/v1/{0}/credentials?access_token={1}'.format(username, access_token),
         match_querystring=True,
         body=query_body, status=200,
         content_type='application/json')
 
-    res = mapbox.Uploader(username, access_token='pk.test')._get_credentials()
+    res = mapbox.Uploader(access_token=access_token)._get_credentials()
     assert res.status_code == 200
     creds = res.json()
     assert username in creds['url']
@@ -48,18 +52,18 @@ def test_get_credentials():
 def test_create():
     responses.add(
         responses.POST,
-        'https://api.mapbox.com/uploads/v1/{0}?access_token=pk.test'.format(username),
+        'https://api.mapbox.com/uploads/v1/{0}?access_token={1}'.format(username, access_token),
         match_querystring=True,
         body=upload_response_body, status=201,
         content_type='application/json')
 
-    res = mapbox.Uploader(username, access_token='pk.test').create(
+    res = mapbox.Uploader(access_token=access_token).create(
         'http://example.com/test.json', 'test1')  # without username prefix
     assert res.status_code == 201
     job = res.json()
     assert job['tileset'] == "{0}.test1".format(username)
 
-    res2 = mapbox.Uploader(username, access_token='pk.test').create(
+    res2 = mapbox.Uploader(access_token=access_token).create(
         'http://example.com/test.json', 'testuser.test1')  # also takes full tileset
     assert res2.status_code == 201
     job = res2.json()
@@ -86,11 +90,11 @@ def test_create_name():
 
     responses.add_callback(
         responses.POST,
-        'https://api.mapbox.com/uploads/v1/{0}?access_token=pk.test'.format(username),
+        'https://api.mapbox.com/uploads/v1/{0}?access_token={1}'.format(username, access_token),
         match_querystring=True,
         callback=request_callback)
 
-    res = mapbox.Uploader(username, access_token='pk.test').create(
+    res = mapbox.Uploader(access_token=access_token).create(
         'http://example.com/test.json', 'testuser.test1', name="testname")
     assert res.status_code == 201
     job = res.json()
@@ -101,12 +105,12 @@ def test_create_name():
 def test_list():
     responses.add(
         responses.GET,
-        'https://api.mapbox.com/uploads/v1/{0}?access_token=pk.test'.format(username),
+        'https://api.mapbox.com/uploads/v1/{0}?access_token={1}'.format(username, access_token),
         match_querystring=True,
         body="[{0}]".format(upload_response_body), status=200,
         content_type='application/json')
 
-    res = mapbox.Uploader(username, access_token='pk.test').list()
+    res = mapbox.Uploader(access_token=access_token).list()
     assert res.status_code == 200
     uploads = res.json()
     assert len(uploads) == 1
@@ -118,12 +122,12 @@ def test_status():
     job = json.loads(upload_response_body)
     responses.add(
         responses.GET,
-        'https://api.mapbox.com/uploads/v1/{0}/{1}?access_token=pk.test'.format(username, job['id']),
+        'https://api.mapbox.com/uploads/v1/{0}/{1}?access_token={2}'.format(username, job['id'], access_token),
         match_querystring=True,
         body=upload_response_body, status=200,
         content_type='application/json')
 
-    res = mapbox.Uploader(username, access_token='pk.test').status(job)
+    res = mapbox.Uploader(access_token=access_token).status(job)
     assert res.status_code == 200
     status = res.json()
     assert job == status
@@ -134,10 +138,10 @@ def test_delete():
     job = json.loads(upload_response_body)
     responses.add(
         responses.DELETE,
-        'https://api.mapbox.com/uploads/v1/{0}/{1}?access_token=pk.test'.format(username, job['id']),
+        'https://api.mapbox.com/uploads/v1/{0}/{1}?access_token={2}'.format(username, job['id'], access_token),
         match_querystring=True,
         body=None, status=204,
         content_type='application/json')
 
-    res = mapbox.Uploader(username, access_token='pk.test').delete(job)
+    res = mapbox.Uploader(access_token=access_token).delete(job)
     assert res.status_code == 204