Stage 1 conversion to JSON for storing Credentials.

Reviewed in http://codereview.appspot.com/4972065/
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)