Stage 1 conversion to JSON for storing Credentials.
Reviewed in http://codereview.appspot.com/4972065/
diff --git a/apiclient/discovery.py b/apiclient/discovery.py
index 5f3c267..2a924a2 100644
--- a/apiclient/discovery.py
+++ b/apiclient/discovery.py
@@ -59,11 +59,12 @@
STACK_QUERY_PARAMETERS = ['trace', 'fields', 'pp', 'prettyPrint', 'userIp',
'userip', 'strict']
-RESERVED_WORDS = [ 'and', 'assert', 'break', 'class', 'continue', 'def', 'del',
+RESERVED_WORDS = ['and', 'assert', 'break', 'class', 'continue', 'def', 'del',
'elif', 'else', 'except', 'exec', 'finally', 'for', 'from',
'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or',
'pass', 'print', 'raise', 'return', 'try', 'while' ]
+
def _fix_method_name(name):
if name in RESERVED_WORDS:
return name + '_'
@@ -242,10 +243,10 @@
return str(value)
MULTIPLIERS = {
- "KB": 2**10,
- "MB": 2**20,
- "GB": 2**30,
- "TB": 2**40,
+ "KB": 2 ** 10,
+ "MB": 2 ** 20,
+ "GB": 2 ** 30,
+ "TB": 2 ** 40,
}
def _media_size_to_long(maxSize):
@@ -255,7 +256,7 @@
units = maxSize[-2:].upper()
multiplier = MULTIPLIERS.get(units, 0)
if multiplier:
- return int(maxSize[:-2])*multiplier
+ return int(maxSize[:-2]) * multiplier
else:
return int(maxSize)
diff --git a/apiclient/model.py b/apiclient/model.py
index 7f51d29..b8271f9 100644
--- a/apiclient/model.py
+++ b/apiclient/model.py
@@ -222,7 +222,8 @@
_abstract()
def deserialize(self, content):
- """Perform the actual deserialization from response string to Python object.
+ """Perform the actual deserialization from response string to Python
+ object.
Args:
content: string, the body of the HTTP response
@@ -285,8 +286,8 @@
de-serialized using the given protocol buffer class.
Args:
- protocol_buffer: The protocol buffer class used to de-serialize a response
- from the API.
+ protocol_buffer: The protocol buffer class used to de-serialize a
+ response from the API.
"""
self._protocol_buffer = protocol_buffer
diff --git a/apiclient/oauth.py b/apiclient/oauth.py
index 18877b0..11eb680 100644
--- a/apiclient/oauth.py
+++ b/apiclient/oauth.py
@@ -377,7 +377,6 @@
return http
-
class FlowThreeLegged(Flow):
"""Does the Three Legged Dance for OAuth 1.0a.
"""
diff --git a/oauth2client/appengine.py b/oauth2client/appengine.py
index 64fd3ac..2811069 100644
--- a/oauth2client/appengine.py
+++ b/oauth2client/appengine.py
@@ -95,6 +95,16 @@
None,
token_uri)
+ @classmethod
+ def from_json(cls, json):
+ data = simplejson.loads(json)
+ retval = AccessTokenCredentials(
+ data['scope'],
+ data['audience'],
+ data['assertion_type'],
+ data['token_uri'])
+ return retval
+
def _generate_assertion(self):
header = {
'typ': 'JWT',
@@ -165,17 +175,28 @@
def get_value_for_datastore(self, model_instance):
cred = super(CredentialsProperty,
self).get_value_for_datastore(model_instance)
- return db.Blob(pickle.dumps(cred))
+ if cred is None:
+ cred = ''
+ else:
+ cred = cred.to_json()
+ return db.Blob(cred)
# For reading from datastore.
def make_value_from_datastore(self, value):
if value is None:
return None
- return pickle.loads(value)
+ if len(value) == 0:
+ return None
+ credentials = None
+ try:
+ credentials = Credentials.new_from_json(value)
+ except ValueError:
+ credentials = pickle.loads(value)
+ return credentials
def validate(self, value):
if value is not None and not isinstance(value, Credentials):
- raise BadValueError('Property %s must be convertible '
+ raise db.BadValueError('Property %s must be convertible '
'to an Credentials instance (%s)' %
(self.name, value))
return super(CredentialsProperty, self).validate(value)
@@ -215,15 +236,15 @@
oauth2client.Credentials
"""
if self._cache:
- credential = self._cache.get(self._key_name)
- if credential:
- return pickle.loads(credential)
+ json = self._cache.get(self._key_name)
+ if json:
+ return Credentials.new_from_json(json)
entity = self._model.get_or_insert(self._key_name)
credential = getattr(entity, self._property_name)
if credential and hasattr(credential, 'set_store'):
credential.set_store(self)
if self._cache:
- self._cache.set(self._key_name, pickle.dumps(credentials))
+ self._cache.set(self._key_name, credentials.to_json())
return credential
@@ -237,7 +258,7 @@
setattr(entity, self._property_name, credentials)
entity.put()
if self._cache:
- self._cache.set(self._key_name, pickle.dumps(credentials))
+ self._cache.set(self._key_name, credentials.to_json())
class CredentialsModel(db.Model):
diff --git a/oauth2client/client.py b/oauth2client/client.py
index 52f6fb3..2b97d4d 100644
--- a/oauth2client/client.py
+++ b/oauth2client/client.py
@@ -43,6 +43,9 @@
logger = logging.getLogger(__name__)
+# Expiry is stored in RFC3339 UTC format
+EXPIRY_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
+
class Error(Exception):
"""Base error for this module."""
@@ -71,10 +74,15 @@
class Credentials(object):
"""Base class for all Credentials objects.
- Subclasses must define an authorize() method
- that applies the credentials to an HTTP transport.
+ Subclasses must define an authorize() method that applies the credentials to
+ an HTTP transport.
+
+ Subclasses must also specify a classmethod named 'from_json' that takes a JSON
+ string as input and returns an instaniated Crentials object.
"""
+ NON_SERIALIZED_MEMBERS = ['store']
+
def authorize(self, http):
"""Take an httplib2.Http instance (or equivalent) and
authorizes it for the set of credentials, usually by
@@ -84,6 +92,58 @@
"""
_abstract()
+ def _to_json(self, strip):
+ """Utility function for creating a JSON representation of an instance of Credentials.
+
+ Args:
+ strip: array, An array of names of members to not include in the JSON.
+
+ Returns:
+ string, a JSON representation of this instance, suitable to pass to
+ from_json().
+ """
+ t = type(self)
+ d = copy.copy(self.__dict__)
+ for member in strip:
+ del d[member]
+ if 'token_expiry' in d and isinstance(d['token_expiry'], datetime.datetime):
+ d['token_expiry'] = d['token_expiry'].strftime(EXPIRY_FORMAT)
+ # Add in information we will need later to reconsistitue this instance.
+ d['_class'] = t.__name__
+ d['_module'] = t.__module__
+ return simplejson.dumps(d)
+
+ def to_json(self):
+ """Creating a JSON representation of an instance of Credentials.
+
+ Returns:
+ string, a JSON representation of this instance, suitable to pass to
+ from_json().
+ """
+ return self._to_json(Credentials.NON_SERIALIZED_MEMBERS)
+
+ @classmethod
+ def new_from_json(cls, s):
+ """Utility class method to instantiate a Credentials subclass from a JSON
+ representation produced by to_json().
+
+ Args:
+ s: string, JSON from to_json().
+
+ Returns:
+ An instance of the subclass of Credentials that was serialized with
+ to_json().
+ """
+ data = simplejson.loads(s)
+ # Find and call the right classmethod from_json() to restore the object.
+ module = data['_module']
+ m = __import__(module)
+ for sub_module in module.split('.')[1:]:
+ m = getattr(m, sub_module)
+ kls = getattr(m, data['_class'])
+ from_json = getattr(kls, 'from_json')
+ return from_json(s)
+
class Flow(object):
"""Base class for all Flow objects."""
@@ -206,6 +266,36 @@
# refreshed.
self.invalid = False
+ def to_json(self):
+ return self._to_json(Credentials.NON_SERIALIZED_MEMBERS)
+
+ @classmethod
+ def from_json(cls, s):
+ """Instantiate a Credentials object from a JSON description of it. The JSON
+ should have been produced by calling .to_json() on the object.
+
+ Args:
+ data: dict, A deserialized JSON object.
+
+ Returns:
+ An instance of a Credentials subclass.
+ """
+ data = simplejson.loads(s)
+ if 'token_expiry' in data and not isinstance(data['token_expiry'],
+ datetime.datetime):
+ data['token_expiry'] = datetime.datetime.strptime(
+ data['token_expiry'], EXPIRY_FORMAT)
+ retval = OAuth2Credentials(
+ data['access_token'],
+ data['client_id'],
+ data['client_secret'],
+ data['refresh_token'],
+ data['token_expiry'],
+ data['token_uri'],
+ data['user_agent'])
+ retval.invalid = data['invalid']
+ return retval
+
@property
def access_token_expired(self):
"""True if the credential is expired or invalid.
@@ -218,7 +308,7 @@
if not self.token_expiry:
return False
- now = datetime.datetime.now()
+ now = datetime.datetime.utcnow()
if now >= self.token_expiry:
logger.info('access_token is expired. Now: %s, token_expiry: %s',
now, self.token_expiry)
@@ -318,7 +408,7 @@
self.refresh_token = d.get('refresh_token', self.refresh_token)
if 'expires_in' in d:
self.token_expiry = datetime.timedelta(
- seconds=int(d['expires_in'])) + datetime.datetime.now()
+ seconds=int(d['expires_in'])) + datetime.datetime.utcnow()
else:
self.token_expiry = None
if self.store:
@@ -446,6 +536,15 @@
None,
user_agent)
+
+ @classmethod
+ def from_json(cls, s):
+ data = simplejson.loads(s)
+ retval = AccessTokenCredentials(
+ data['access_token'],
+ data['user_agent'])
+ return retval
+
def _refresh(self, http_request):
raise AccessTokenCredentialsError(
"The access_token is expired or invalid and can't be refreshed.")
@@ -601,7 +700,7 @@
refresh_token = d.get('refresh_token', None)
token_expiry = None
if 'expires_in' in d:
- token_expiry = datetime.datetime.now() + datetime.timedelta(
+ token_expiry = datetime.datetime.utcnow() + datetime.timedelta(
seconds=int(d['expires_in']))
logger.info('Successfully retrieved access token: %s' % content)
diff --git a/oauth2client/file.py b/oauth2client/file.py
index b7f9c7d..89140b8 100644
--- a/oauth2client/file.py
+++ b/oauth2client/file.py
@@ -23,7 +23,20 @@
import pickle
import threading
+
+try: # pragma: no cover
+ import simplejson
+except ImportError: # pragma: no cover
+ try:
+ # Try to import from django, should work on App Engine
+ from django.utils import simplejson
+ except ImportError:
+ # Should work for Python2.6 and higher.
+ import json as simplejson
+
+
from client import Storage as BaseStorage
+from client import Credentials
class Storage(BaseStorage):
@@ -40,25 +53,40 @@
oauth2client.client.Credentials
"""
self._lock.acquire()
+ credentials = None
try:
f = open(self._filename, 'r')
- credentials = pickle.loads(f.read())
+ content = f.read()
f.close()
+ except IOError:
+ self._lock.release()
+ return credentials
+
+ # First try reading as JSON, and if that fails fall back to pickle.
+ try:
+ credentials = Credentials.new_from_json(content)
credentials.set_store(self)
- except:
- credentials = None
- self._lock.release()
+ except ValueError:
+ # TODO(jcgregorio) On a future release remove this path to finally remove
+ # all pickle support.
+ try:
+ credentials = pickle.loads(content)
+ credentials.set_store(self)
+ except:
+ pass
+ finally:
+ self._lock.release()
return credentials
def put(self, credentials):
- """Write a pickled Credentials to file.
+ """Write Credentials to file.
Args:
credentials: Credentials, the credentials to store.
"""
self._lock.acquire()
f = open(self._filename, 'w')
- f.write(pickle.dumps(credentials))
+ f.write(credentials.to_json())
f.close()
self._lock.release()
diff --git a/oauth2client/multistore_file.py b/oauth2client/multistore_file.py
index 8841194..e3e3f6d 100644
--- a/oauth2client/multistore_file.py
+++ b/oauth2client/multistore_file.py
@@ -21,7 +21,9 @@
'userAgent': '<user agent>',
'scope': '<scope>'
},
- 'credential': '<base64 encoding of pickeled Credential object>'
+ 'credential': {
+ # JSON serialized Credentials.
+ }
}
]
}
@@ -47,6 +49,7 @@
import json as simplejson
from client import Storage as BaseStorage
+from client import Credentials
logger = logging.getLogger(__name__)
@@ -295,7 +298,8 @@
user_agent = raw_key['userAgent']
scope = raw_key['scope']
key = (client_id, user_agent, scope)
- credential = pickle.loads(base64.b64decode(cred_entry['credential']))
+ credential = None
+ credential = Credentials.new_from_json(simplejson.dumps(cred_entry['credential']))
return (key, credential)
def _write(self):
@@ -312,7 +316,7 @@
'userAgent': cred_key[1],
'scope': cred_key[2]
}
- raw_cred = base64.b64encode(pickle.dumps(cred))
+ raw_cred = simplejson.loads(cred.to_json())
raw_creds.append({'key': raw_key, 'credential': raw_cred})
self._locked_json_write(raw_data)
@@ -330,6 +334,7 @@
The credential specified or None if not present
"""
key = (client_id, user_agent, scope)
+
return self._data.get(key, None)
def _update_credential(self, cred, scope):
diff --git a/oauth2client/tools.py b/oauth2client/tools.py
index dc779b4..574a747 100644
--- a/oauth2client/tools.py
+++ b/oauth2client/tools.py
@@ -129,12 +129,16 @@
print '--noauth_local_webserver.'
print
+ code = None
if FLAGS.auth_local_webserver:
httpd.handle_request()
if 'error' in httpd.query_params:
sys.exit('Authentication request was rejected.')
if 'code' in httpd.query_params:
code = httpd.query_params['code']
+ else:
+ print 'Failed to find "code" in the query parameters of the redirect.'
+ sys.exit('Try running with --noauth_local_webserver.')
else:
code = raw_input('Enter verification code: ').strip()
diff --git a/samples/appengine_with_decorator2/main.py b/samples/appengine_with_decorator2/main.py
index 03963f4..4cd2401 100644
--- a/samples/appengine_with_decorator2/main.py
+++ b/samples/appengine_with_decorator2/main.py
@@ -43,8 +43,7 @@
decorator = OAuth2Decorator(
client_id='837647042410-75ifgipj95q4agpm0cs452mg7i2pn17c.apps.googleusercontent.com',
client_secret='QhxYsjM__u4vy5N0DXUFRwwI',
- scope='https://www.googleapis.com/auth/buzz',
- user_agent='my-sample-app/1.0')
+ scope='https://www.googleapis.com/auth/buzz')
http = httplib2.Http(memcache)
service = build("buzz", "v1", http=http)
diff --git a/samples/buzz/buzz.py b/samples/buzz/buzz.py
index 2fb4e85..8906048 100644
--- a/samples/buzz/buzz.py
+++ b/samples/buzz/buzz.py
@@ -88,6 +88,7 @@
# Credentials will get written back to a file.
storage = Storage('buzz.dat')
credentials = storage.get()
+
if credentials is None or credentials.invalid:
credentials = run(FLOW, storage)
@@ -112,31 +113,10 @@
activitylist = activities.list_next(activitylist).execute()
print "Retrieved the next two activities"
- # Add a new activity
- new_activity_body = {
- 'title': 'Testing insert',
- 'object': {
- 'content':
- u'Just a short note to show that insert is working. ☄',
- 'type': 'note'}
- }
- activity = activities.insert(userId='@me', body=new_activity_body).execute()
- print "Added a new activity"
-
- activitylist = activities.list(
- max_results='2', scope='@self', userId='@me').execute()
-
- # Add a comment to that activity
- comment_body = {
- "content": "This is a comment"
- }
- item = activitylist['items'][0]
- comment = service.comments().insert(
- userId=item['actor']['id'], postId=item['id'], body=comment_body
- ).execute()
- print 'Added a comment to the new activity'
- pprint.pprint(comment)
-
+ # List the number of followers
+ followers = service.people().list(
+ userId='@me', groupId='@followers').execute(http)
+ print 'Hello, you have %s followers!' % followers['totalResults']
except AccessTokenRefreshError:
print ("The credentials have been revoked or expired, please re-run"
diff --git a/samples/moderator/moderator.py b/samples/moderator/moderator.py
index b7da058..e92ef1b 100644
--- a/samples/moderator/moderator.py
+++ b/samples/moderator/moderator.py
@@ -140,8 +140,6 @@
body=vote_body)
print "Voted on the submission"
-
-
except AccessTokenRefreshError:
print ("The credentials have been revoked or expired, please re-run"
"the application to re-authorize")
diff --git a/samples/prediction/prediction.py b/samples/prediction/prediction.py
index 3ffa974..1227019 100644
--- a/samples/prediction/prediction.py
+++ b/samples/prediction/prediction.py
@@ -109,7 +109,7 @@
# Start training on a data set
train = service.training()
- body = {'id' : FLAGS.object_name}
+ body = {'id': FLAGS.object_name}
start = train.insert(body=body).execute()
print 'Started training'
diff --git a/samples/src/moderator.py b/samples/src/moderator.py
index 1ab9ea8..e27b811 100644
--- a/samples/src/moderator.py
+++ b/samples/src/moderator.py
@@ -43,4 +43,3 @@
submissionId=submission['id']['submissionId'],
body=vote_body)
print "Voted on the submission"
-
diff --git a/tests/test_oauth2client.py b/tests/test_oauth2client.py
index de11810..14beb97 100644
--- a/tests/test_oauth2client.py
+++ b/tests/test_oauth2client.py
@@ -22,6 +22,7 @@
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+import datetime
import httplib2
import unittest
import urlparse
@@ -31,6 +32,16 @@
except ImportError:
from cgi import parse_qs
+try: # pragma: no cover
+ import simplejson
+except ImportError: # pragma: no cover
+ try:
+ # Try to import from django, should work on App Engine
+ from django.utils import simplejson
+ except ImportError:
+ # Should work for Python2.6 and higher.
+ import json as simplejson
+
from apiclient.http import HttpMockSequence
from oauth2client.client import AccessTokenCredentials
from oauth2client.client import AccessTokenCredentialsError
@@ -48,7 +59,7 @@
client_id = "some_client_id"
client_secret = "cOuDdkfjxxnv+"
refresh_token = "1/0/a.df219fjls0"
- token_expiry = "ignored"
+ token_expiry = datetime.datetime.utcnow()
token_uri = "https://www.google.com/accounts/o8/oauth2/token"
user_agent = "refresh_checker/1.0"
self.credentials = OAuth2Credentials(
@@ -86,6 +97,12 @@
resp, content = http.request("http://example.com")
self.assertEqual(400, resp.status)
+ def test_to_from_json(self):
+ json = self.credentials.to_json()
+ instance = OAuth2Credentials.from_json(json)
+ self.assertEquals(type(instance), OAuth2Credentials)
+ self.assertEquals(self.credentials.__dict__, instance.__dict__)
+
class AccessTokenCredentialsTests(unittest.TestCase):
diff --git a/tests/test_oauth2client_appengine.py b/tests/test_oauth2client_appengine.py
index f9b5094..9362220 100644
--- a/tests/test_oauth2client_appengine.py
+++ b/tests/test_oauth2client_appengine.py
@@ -173,7 +173,7 @@
self.assertEqual('code', q['response_type'][0])
self.assertEqual(False, self.decorator.has_credentials())
- # Now simulate the callback to /oauth2callback
+ # Now simulate the callback to /oauth2callback.
response = self.app.get('/oauth2callback', {
'code': 'foo_access_code',
'state': 'foo_path',
@@ -181,7 +181,7 @@
self.assertEqual('http://localhost/foo_path', response.headers['Location'])
self.assertEqual(None, self.decorator.credentials)
- # Now requesting the decorated path should work
+ # Now requesting the decorated path should work.
response = self.app.get('/foo_path')
self.assertEqual('200 OK', response.status)
self.assertEqual(True, self.decorator.has_credentials())
@@ -190,18 +190,18 @@
self.assertEqual('foo_access_token',
self.decorator.credentials.access_token)
- # Invalidate the stored Credentials
+ # Invalidate the stored Credentials.
self.decorator.credentials.invalid = True
self.decorator.credentials.store.put(self.decorator.credentials)
- # Invalid Credentials should start the OAuth dance again
+ # Invalid Credentials should start the OAuth dance again.
response = self.app.get('/foo_path')
self.assertTrue(response.status.startswith('302'))
q = parse_qs(response.headers['Location'].split('?', 1)[1])
self.assertEqual('http://localhost/oauth2callback', q['redirect_uri'][0])
def test_aware(self):
- # An initial request to an oauth_aware decorated path should not redirect
+ # An initial request to an oauth_aware decorated path should not redirect.
response = self.app.get('/bar_path')
self.assertEqual('Hello World!', response.body)
self.assertEqual('200 OK', response.status)
@@ -214,7 +214,7 @@
self.assertEqual('http://localhost/bar_path', q['state'][0])
self.assertEqual('code', q['response_type'][0])
- # Now simulate the callback to /oauth2callback
+ # Now simulate the callback to /oauth2callback.
url = self.decorator.authorize_url()
response = self.app.get('/oauth2callback', {
'code': 'foo_access_code',
@@ -223,7 +223,7 @@
self.assertEqual('http://localhost/bar_path', response.headers['Location'])
self.assertEqual(False, self.decorator.has_credentials())
- # Now requesting the decorated path will have credentials
+ # Now requesting the decorated path will have credentials.
response = self.app.get('/bar_path')
self.assertEqual('200 OK', response.status)
self.assertEqual('Hello World!', response.body)
diff --git a/tests/test_oauth2client_file.py b/tests/test_oauth2client_file.py
new file mode 100644
index 0000000..05deaa0
--- /dev/null
+++ b/tests/test_oauth2client_file.py
@@ -0,0 +1,157 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2010 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""Oauth2client.file tests
+
+Unit tests for oauth2client.file
+"""
+
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
+import os
+import pickle
+import unittest
+import datetime
+
+
+try: # pragma: no cover
+ import simplejson
+except ImportError: # pragma: no cover
+ try:
+ # Try to import from django, should work on App Engine
+ from django.utils import simplejson
+ except ImportError:
+ # Should work for Python2.6 and higher.
+ import json as simplejson
+
+
+from oauth2client.client import OAuth2Credentials
+from oauth2client.client import AccessTokenCredentials
+from oauth2client.client import AssertionCredentials
+from oauth2client.file import Storage
+from oauth2client import multistore_file
+
+
+FILENAME = os.path.join(os.path.dirname(__file__), 'test_file_storage.data')
+
+
+class OAuth2ClientFileTests(unittest.TestCase):
+
+ def tearDown(self):
+ try:
+ os.unlink(FILENAME)
+ except OSError:
+ pass
+
+ def setUp(self):
+ try:
+ os.unlink(FILENAME)
+ except OSError:
+ pass
+
+ def test_non_existent_file_storage(self):
+ s = Storage(FILENAME)
+ credentials = s.get()
+ self.assertEquals(None, credentials)
+
+ def test_pickle_and_json_interop(self):
+ # Write a file with a pickled OAuth2Credentials.
+ access_token = 'foo'
+ client_id = 'some_client_id'
+ client_secret = 'cOuDdkfjxxnv+'
+ refresh_token = '1/0/a.df219fjls0'
+ token_expiry = datetime.datetime.utcnow()
+ token_uri = 'https://www.google.com/accounts/o8/oauth2/token'
+ user_agent = 'refresh_checker/1.0'
+
+ credentials = OAuth2Credentials(
+ access_token, client_id, client_secret,
+ refresh_token, token_expiry, token_uri,
+ user_agent)
+
+ f = open(FILENAME, 'w')
+ pickle.dump(credentials, f)
+ f.close()
+
+ # Storage should be able to read that object.
+ # TODO(jcgregorio) This should fail once pickle support is removed.
+ s = Storage(FILENAME)
+ credentials = s.get()
+ self.assertNotEquals(None, credentials)
+ self.assertEquals('foo', credentials.access_token)
+
+ # Now write it back out and confirm it has been rewritten as JSON
+ s.put(credentials)
+ f = file(FILENAME)
+ data = simplejson.load(f)
+ f.close()
+
+ self.assertEquals(data['access_token'], 'foo')
+ self.assertEquals(data['_class'], 'OAuth2Credentials')
+ self.assertEquals(data['_module'], 'oauth2client.client')
+
+ def test_access_token_credentials(self):
+ access_token = 'foo'
+ user_agent = 'refresh_checker/1.0'
+
+ credentials = AccessTokenCredentials(access_token, user_agent)
+
+ s = Storage(FILENAME)
+ credentials = s.put(credentials)
+ credentials = s.get()
+
+ self.assertNotEquals(None, credentials)
+ self.assertEquals('foo', credentials.access_token)
+
+ def test_multistore_non_existent_file(self):
+ store = multistore_file.get_credential_storage(
+ FILENAME,
+ 'some_client_id',
+ 'user-agent/1.0',
+ 'some-scope')
+
+ credentials = store.get()
+ self.assertEquals(None, credentials)
+
+ def test_multistore_file(self):
+ access_token = 'foo'
+ client_secret = 'cOuDdkfjxxnv+'
+ refresh_token = '1/0/a.df219fjls0'
+ token_expiry = datetime.datetime.utcnow()
+ token_uri = 'https://www.google.com/accounts/o8/oauth2/token'
+ user_agent = 'refresh_checker/1.0'
+ client_id = 'some_client_id'
+
+ credentials = OAuth2Credentials(
+ access_token, client_id, client_secret,
+ refresh_token, token_expiry, token_uri,
+ user_agent)
+
+ store = multistore_file.get_credential_storage(
+ FILENAME,
+ credentials.client_id,
+ credentials.user_agent,
+ 'some-scope')
+
+ store.put(credentials)
+ credentials = store.get()
+
+ self.assertNotEquals(None, credentials)
+ self.assertEquals('foo', credentials.access_token)
+
+if __name__ == '__main__':
+ unittest.main()