Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit a41a960

Browse files
committed
Move client session objects to new module
1 parent ef15286 commit a41a960

File tree

6 files changed

+258
-234
lines changed

6 files changed

+258
-234
lines changed

src/cloudant/_common_util.py

Lines changed: 1 addition & 208 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,12 @@
1717
throughout the library.
1818
"""
1919

20-
import os
2120
import sys
2221
import platform
2322
from collections import Sequence
2423
import json
25-
from requests import RequestException, Session
2624

27-
from ._2to3 import LONGTYPE, STRTYPE, NONETYPE, UNITYPE, iteritems_, url_join
25+
from ._2to3 import LONGTYPE, STRTYPE, NONETYPE, UNITYPE, iteritems_
2826
from .error import CloudantArgumentError, CloudantException
2927

3028
# Library Constants
@@ -290,211 +288,6 @@ def __new__(cls, code):
290288
return str.__new__(cls, code)
291289

292290

293-
class ClientSession(Session):
294-
"""
295-
This class extends Session and provides a default timeout.
296-
"""
297-
298-
def __init__(self, **kwargs):
299-
super(ClientSession, self).__init__()
300-
self._timeout = kwargs.get('timeout', None)
301-
302-
def request(self, method, url, **kwargs): # pylint: disable=W0221
303-
"""
304-
Overrides ``requests.Session.request`` to set the timeout.
305-
"""
306-
resp = super(ClientSession, self).request(
307-
method, url, timeout=self._timeout, **kwargs)
308-
309-
return resp
310-
311-
312-
class CookieSession(ClientSession):
313-
"""
314-
This class extends ClientSession and provides cookie authentication.
315-
"""
316-
317-
def __init__(self, username, password, server_url, **kwargs):
318-
super(CookieSession, self).__init__(**kwargs)
319-
self._username = username
320-
self._password = password
321-
self._auto_renew = kwargs.get('auto_renew', False)
322-
self._session_url = url_join(server_url, '_session')
323-
324-
def info(self):
325-
"""
326-
Get cookie based login user information.
327-
"""
328-
resp = self.get(self._session_url)
329-
resp.raise_for_status()
330-
331-
return resp.json()
332-
333-
def login(self):
334-
"""
335-
Perform cookie based user login.
336-
"""
337-
resp = super(CookieSession, self).request(
338-
'POST',
339-
self._session_url,
340-
data={'name': self._username, 'password': self._password},
341-
)
342-
resp.raise_for_status()
343-
344-
def logout(self):
345-
"""
346-
Logout cookie based user.
347-
"""
348-
resp = super(CookieSession, self).request('DELETE', self._session_url)
349-
resp.raise_for_status()
350-
351-
def request(self, method, url, **kwargs): # pylint: disable=W0221
352-
"""
353-
Overrides ``requests.Session.request`` to renew the cookie and then
354-
retry the original request (if required).
355-
"""
356-
resp = super(CookieSession, self).request(method, url, **kwargs)
357-
358-
if not self._auto_renew:
359-
return resp
360-
361-
is_expired = any((
362-
resp.status_code == 403 and
363-
resp.json().get('error') == 'credentials_expired',
364-
resp.status_code == 401
365-
))
366-
367-
if is_expired:
368-
self.login()
369-
resp = super(CookieSession, self).request(method, url, **kwargs)
370-
371-
return resp
372-
373-
def set_credentials(self, username, password):
374-
"""
375-
Set a new username and password.
376-
377-
:param str username: New username.
378-
:param str password: New password.
379-
"""
380-
if username is not None:
381-
self._username = username
382-
383-
if password is not None:
384-
self._password = password
385-
386-
387-
class IAMSession(ClientSession):
388-
"""
389-
This class extends ClientSession and provides IAM authentication.
390-
"""
391-
392-
def __init__(self, api_key, server_url, **kwargs):
393-
super(IAMSession, self).__init__(**kwargs)
394-
self._api_key = api_key
395-
self._auto_renew = kwargs.get('auto_renew', False)
396-
self._session_url = url_join(server_url, '_iam_session')
397-
self._token_url = os.environ.get(
398-
'IAM_TOKEN_URL', 'https://iam.bluemix.net/oidc/token')
399-
400-
def info(self):
401-
"""
402-
Get IAM cookie based login user information.
403-
"""
404-
resp = self.get(self._session_url)
405-
resp.raise_for_status()
406-
407-
return resp.json()
408-
409-
def login(self):
410-
"""
411-
Perform IAM cookie based user login.
412-
"""
413-
access_token = self._get_access_token()
414-
try:
415-
super(IAMSession, self).request(
416-
'POST',
417-
self._session_url,
418-
headers={'Content-Type': 'application/json'},
419-
data=json.dumps({'access_token': access_token})
420-
).raise_for_status()
421-
422-
except RequestException:
423-
raise CloudantException(
424-
'Failed to exchange IAM token with Cloudant')
425-
426-
def logout(self):
427-
"""
428-
Logout IAM cookie based user.
429-
"""
430-
self.cookies.clear()
431-
432-
def request(self, method, url, **kwargs): # pylint: disable=W0221
433-
"""
434-
Overrides ``requests.Session.request`` to renew the IAM cookie
435-
and then retry the original request (if required).
436-
"""
437-
# The CookieJar API prevents callers from getting an individual Cookie
438-
# object by name.
439-
# We are forced to use the only exposed method of discarding expired
440-
# cookies from the CookieJar. Internally this involves iterating over
441-
# the entire CookieJar and calling `.is_expired()` on each Cookie
442-
# object.
443-
self.cookies.clear_expired_cookies()
444-
445-
if self._auto_renew and 'IAMSession' not in self.cookies.keys():
446-
self.login()
447-
448-
resp = super(IAMSession, self).request(method, url, **kwargs)
449-
450-
if not self._auto_renew:
451-
return resp
452-
453-
if resp.status_code == 401:
454-
self.login()
455-
resp = super(IAMSession, self).request(method, url, **kwargs)
456-
457-
return resp
458-
459-
def set_credentials(self, username, api_key):
460-
"""
461-
Set a new IAM API key.
462-
463-
:param str username: Username parameter is unused.
464-
:param str api_key: New IAM API key.
465-
"""
466-
if api_key is not None:
467-
self._api_key = api_key
468-
469-
def _get_access_token(self):
470-
"""
471-
Get IAM access token using API key.
472-
"""
473-
err = 'Failed to contact IAM token service'
474-
try:
475-
resp = super(IAMSession, self).request(
476-
'POST',
477-
self._token_url,
478-
auth=('bx', 'bx'), # required for user API keys
479-
headers={'Accepts': 'application/json'},
480-
data={
481-
'grant_type': 'urn:ibm:params:oauth:grant-type:apikey',
482-
'response_type': 'cloud_iam',
483-
'apikey': self._api_key
484-
}
485-
)
486-
err = resp.json().get('errorMessage', err)
487-
resp.raise_for_status()
488-
489-
return resp.json()['access_token']
490-
491-
except KeyError:
492-
raise CloudantException('Invalid response from IAM token service')
493-
494-
except RequestException:
495-
raise CloudantException(err)
496-
497-
498291
class CloudFoundryService(object):
499292
""" Manages Cloud Foundry service configuration. """
500293

src/cloudant/client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@
2929
from ._common_util import (
3030
USER_AGENT,
3131
append_response_error_content,
32-
ClientSession,
3332
CloudFoundryService,
34-
CookieSession,
35-
IAMSession)
33+
)
34+
from client_session import ClientSession, CookieSession, IAMSession
35+
3636

3737
class CouchDB(dict):
3838
"""

0 commit comments

Comments
 (0)