Repository: nithinmurali/pygsheets

Issue:
Token has been expired or revoked
Hello! How can I disable frequent refresh of the Token?
Quite often, you have to renew the token for the errors “Token has been expired or revoked.”

Discussion:

Patch:
diff --git a/pygsheets/authorization.py b/pygsheets/authorization.py
index 3da1580..7c921c3 100644
--- a/pygsheets/authorization.py
+++ b/pygsheets/authorization.py
@@ -5,6 +5,7 @@ import warnings
 
 from google.oauth2 import service_account
 from google.oauth2.credentials import Credentials
+from google.auth.exceptions import RefreshError
 from google_auth_oauthlib.flow import Flow, InstalledAppFlow
 from google.auth.transport.requests import Request
 
@@ -16,6 +17,22 @@ except NameError:
     pass
 
 
+def _get_initial_user_authentication_credentials(client_secret_file, local, scopes):
+    if local:
+        flow = InstalledAppFlow.from_client_secrets_file(client_secret_file, scopes)
+        credentials = flow.run_local_server()
+    else:
+        flow = Flow.from_client_secrets_file(client_secret_file, scopes=scopes,
+                                             redirect_uri='urn:ietf:wg:oauth:2.0:oob')
+        auth_url, _ = flow.authorization_url(prompt='consent')
+
+        print('Please go to this URL and finish the authentication flow: {}'.format(auth_url))
+        code = input('Enter the authorization code: ')
+        flow.fetch_token(code=code)
+        credentials = flow.credentials
+    return credentials
+
+
 def _get_user_authentication_credentials(client_secret_file, scopes, credential_directory=None, local=False):
     """Returns user credentials."""
     if credential_directory is None:
@@ -37,20 +54,13 @@ def _get_user_authentication_credentials(client_secret_file, scopes, credential_
 
     if credentials:
         if credentials.expired and credentials.refresh_token:
-            credentials.refresh(Request())
+            try:
+                credentials.refresh(Request())
+            except RefreshError as exc:
+                print(f'Refresh token is obsolete {exc}. Executing the initial flow')
+                credentials = _get_initial_user_authentication_credentials(client_secret_file, local, scopes)
     else:
-        if local:
-            flow = InstalledAppFlow.from_client_secrets_file(client_secret_file, scopes)
-            credentials = flow.run_local_server()
-        else:
-            flow = Flow.from_client_secrets_file(client_secret_file, scopes=scopes,
-                                                 redirect_uri='urn:ietf:wg:oauth:2.0:oob')
-            auth_url, _ = flow.authorization_url(prompt='consent')
-
-            print('Please go to this URL and finish the authentication flow: {}'.format(auth_url))
-            code = input('Enter the authorization code: ')
-            flow.fetch_token(code=code)
-            credentials = flow.credentials
+        credentials = _get_initial_user_authentication_credentials(client_secret_file, local, scopes)
 
     # Save the credentials for the next run
     credentials_as_dict = {


Test patch:
diff --git a/tests/authorization_test.py b/tests/authorization_test.py
index d2abca3..8b5073d 100644
--- a/tests/authorization_test.py
+++ b/tests/authorization_test.py
@@ -1,8 +1,12 @@
+from google.auth.exceptions import RefreshError
+from google.oauth2.credentials import Credentials
 import os
 import sys
+from unittest.mock import patch
 
 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 import pygsheets
+from pygsheets.authorization import _SCOPES
 from pygsheets.client import Client
 
 from googleapiclient.http import HttpError
@@ -38,3 +42,35 @@ class TestAuthorization(object):
         c = pygsheets.authorize(service_file=self.base_path + '/pygsheettest_service_account.json', retries=3)
         assert isinstance(c, Client)
         assert c.sheet.retries == 3
+
+    def test_should_reload_client_secret_on_refresh_error(self):
+        # First connection
+        initial_c = pygsheets.authorize(
+            client_secret=self.base_path + "/client_secret.json",
+            credentials_directory=self.base_path,
+        )
+        credentials_filepath = self.base_path + "/sheets.googleapis.com-python.json"
+        assert os.path.exists(credentials_filepath)
+
+        # After a while, the refresh token is not working and raises RefreshError
+        refresh_c = None
+        with patch(
+            "pygsheets.authorization._get_initial_user_authentication_credentials"
+        ) as mock_initial_credentials:
+            real_credentials = Credentials.from_authorized_user_file(
+                credentials_filepath, scopes=_SCOPES
+            )
+            mock_initial_credentials.return_value = real_credentials
+
+            with patch("pygsheets.authorization.Credentials") as mock_credentials:
+                mock_credentials.from_authorized_user_file.return_value.refresh.side_effect = RefreshError(
+                    "Error using refresh token"
+                )
+                mock_initial_credentials
+                refresh_c = pygsheets.authorize(
+                    client_secret=self.base_path + "/client_secret.json",
+                    credentials_directory=self.base_path,
+                )
+
+            mock_initial_credentials.assert_called_once()
+        assert isinstance(refresh_c, Client)