Skip to content

Instantly share code, notes, and snippets.

@franfabrizio
Last active November 8, 2023 16:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save franfabrizio/b33b3af8ecb75cd9d0a2004ed6e5ef97 to your computer and use it in GitHub Desktop.
Save franfabrizio/b33b3af8ecb75cd9d0a2004ed6e5ef97 to your computer and use it in GitHub Desktop.
Python example using refresh token to update access token for Mendeley API using the Mendeley Python SDK
from mendeley import Mendeley
from mendeley.exception import MendeleyException
from mendeley.session import MendeleySession
# extending the MendeleySession class to do auto-token-refresh on
# token expiration. Mendeley access tokens expire after 1 hour.
class AutoRefreshMendeleySession(MendeleySession):
def __init__(self, mendeley, token, refresh_token):
super(AutoRefreshMendeleySession, self).__init__(mendeley, token)
# silly name to avoid namespace collision with oauth refresh_token() method
self.the_refresh_token = refresh_token
self.client_secret = mendeley.client_secret
def request(self, method, url, data=None, headers=None, **kwargs):
try:
# just try the MendeleySession request first
return super(AutoRefreshMendeleySession, self).request(method, url, data, headers, **kwargs)
except (MendeleyApiException, TokenExpiredError) as e:
print ("Receiving " + type(e).__name__)
# Check to see if we have an expired access token. This comes in two
# forms: either a MendeleyApiException or OAuthlib's TokenExpiredError
#
# Mendeley's API uses MendeleyAPIException for everything so you have
# to unpack it and inspect to see if it's a token expiration or not.
# In event of an expired token it sends back a 401 with the JSON message
# {"message": "Could not access resource because: Token has expired"}
# and the Python SDK forms a MendeleyAPIException with this message
# in e.message and 401 in e.status
# OAuthlib will send a TokenExpiredError if you have a long-running
# session that goes over an hour. MendeleyApiException occurs when
# you try to make a first request with an already expired token (such
# as the one that's probably in your app's config file.)
if ((type(e).__name__ is 'MendeleyApiException') and (e.status == 401) and ('Token has expired' in e.message)) or (type(e).__name__ is 'TokenExpiredError'):
print ("Handling a token expiration of type " + type(e).__name__)
self.refresh_token('https://api.mendeley.com/oauth/token', self.the_refresh_token, auth=(self.client_id, self.client_secret), redirect_uri="www.ipums.org")
return super(AutoRefreshMendeleySession, self).request(method, url, data, headers, **kwargs)
else:
print ("Re-raising " + type(e).__name__)
# pass on other mendeley exceptions
raise
# example use
mendeley = Mendeley(CLIENT_ID, CLIENT_SECRET)
session = AutoRefreshMendeleySession(mendeley,{'access_token': ACCESS_TOKEN, 'token_type': "Bearer"}, REFRESH_TOKEN)
# This will call the API, refreshing the token if needed
for document in session.documents.iter():
print (document.title)
@franfabrizio
Copy link
Author

The basic idea is to extend MendeleySession's request() method to try the API call, check the return for an expired token message, and if found, refresh the token and do the original call again. To do the actual refresh, I'm calling the refresh_token() method from OAuth2Session directly (MendeleySession inherits from OAuth2Session). Mendeley's Python SDK didn't support this sort of operation - much of that SDK assumes a webapp where users could be redirected to a login page again if the token was expired. That wasn't my use case, so I needed this strategy.

@franfabrizio
Copy link
Author

I just updated the gist because I learned that while a MendeleyApiException is raised when an expired token is used at the start of a session, but if an hour elapses an OAuthlib TokenExpiredError is raised instead. So now the gist handles both cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment