Skip to content

Commit 59c2b57

Browse files
committed
refactor: make session auth to use base service's http client
1 parent 365d724 commit 59c2b57

4 files changed

Lines changed: 61 additions & 40 deletions

File tree

‎ibmcloudant/cloudant_base_service.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
from ibm_cloud_sdk_core import BaseService
2828
from ibm_cloud_sdk_core.authenticators import Authenticator
2929
from requests import Response, Session
30-
from requests.cookies import RequestsCookieJar
3130

3231
from .common import get_sdk_headers
3332
from .couchdb_session_authenticator import CouchDbSessionAuthenticator
@@ -96,9 +95,8 @@ def __init__(
9695
self.set_http_config(new_http_config)
9796
# Custom actions for CouchDbSessionAuthenticator
9897
if isinstance(authenticator, CouchDbSessionAuthenticator):
99-
# Replacing BaseService's http.cookiejar.CookieJar as RequestsCookieJar supports update(CookieJar)
100-
self.jar = RequestsCookieJar(self.jar)
101-
self.authenticator.set_jar(self.jar) # Authenticators don't have access to cookie jars by default
98+
# Make token manager of CouchDbSessionAuthenticator to use the same http client as main service
99+
self.authenticator._set_http_client(self.get_http_client(), self.jar)
102100
add_hooks(self)
103101

104102
def set_service_url(self, service_url: str):
@@ -128,6 +126,8 @@ def set_disable_ssl_verification(self, status: bool = False) -> None:
128126

129127
def set_http_client(self, http_client: Session) -> None:
130128
super().set_http_client(http_client)
129+
if isinstance(self.authenticator, CouchDbSessionAuthenticator):
130+
self.authenticator._set_http_client(self.get_http_client(), self.jar)
131131
add_hooks(self)
132132

133133
def prepare_request(self,

‎ibmcloudant/couchdb_session_authenticator.py‎

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# coding: utf-8
22

3-
# © Copyright IBM Corporation 2020, 2022.
3+
# © Copyright IBM Corporation 2020, 2025.
44
#
55
# Licensed under the Apache License, Version 2.0 (the "License");
66
# you may not use this file except in compliance with the License.
@@ -16,7 +16,9 @@
1616
"""
1717
Module for handling session authentication
1818
"""
19-
from requests import Request
19+
from requests import Request, Session
20+
from requests.cookies import RequestsCookieJar
21+
2022

2123
from ibm_cloud_sdk_core.authenticators import Authenticator
2224
from .couchdb_session_token_manager import CouchDbSessionTokenManager
@@ -46,20 +48,22 @@ def __init__(self,
4648
if not isinstance(disable_ssl_verification, bool):
4749
raise TypeError('disable_ssl_verification must be a bool')
4850

49-
self.jar = None
50-
5151
self.token_manager = CouchDbSessionTokenManager(
5252
username,
5353
password,
5454
disable_ssl_verification=disable_ssl_verification
5555
)
5656
self.validate()
5757

58-
def set_jar(self, jar):
59-
"""Sets the cookie jar for the authenticator.
58+
def _set_http_client(self, http_client: Session, jar: RequestsCookieJar) -> None:
59+
"""Sets base serivice's http client for the authenticator.
6060
This is an internal method called by BaseService. Not to be called directly.
6161
"""
62-
self.jar = jar
62+
if isinstance(http_client, Session):
63+
self.token_manager.http_client = http_client
64+
self.token_manager.jar = jar
65+
else:
66+
raise TypeError("http_client parameter must be a requests.sessions.Session")
6367

6468
def validate(self):
6569
"""Validates the username, and password for session token requests.
@@ -82,11 +86,7 @@ def authenticate(self, req: Request):
8286
Args:
8387
req: Ignored. BaseService uses the cookie jar for every request
8488
"""
85-
jar = self.token_manager.get_token()
86-
# Requests seem to save cookies only for Sessions. BaseService is
87-
# hard-coded to work with "regular" requests requests so updating
88-
# the jar manually is necessary
89-
self.jar.update(jar)
89+
self.token_manager.get_token()
9090

9191
def authentication_type(self) -> str:
9292
"""Returns this authenticator's type ('COUCHDB_SESSION')."""

‎ibmcloudant/couchdb_session_token_manager.py‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# coding: utf-8
22

3-
# © Copyright IBM Corporation 2020, 2022.
3+
# © Copyright IBM Corporation 2020, 2025.
44
#
55
# Licensed under the Apache License, Version 2.0 (the "License");
66
# you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
1616
"""
1717
Module for managing session authentication token
1818
"""
19+
from requests import Session
20+
1921
from ibm_cloud_sdk_core.token_managers.token_manager import TokenManager
2022

2123

@@ -52,7 +54,9 @@ def __init__(self, username: str, password: str,
5254

5355
self.token = None
5456

57+
self.http_client = None
5558
self.http_config = {}
59+
self.jar = None
5660
self.headers = None
5761

5862
def request_token(self):
@@ -63,14 +67,19 @@ def request_token(self):
6367
A CookieJar of Cookies the server sent back.
6468
"""
6569

66-
response = self._request(
70+
if not isinstance(self.http_client, Session):
71+
raise TypeError("http_client parameter must be a requests.sessions.Session")
72+
73+
response = self.http_client.request(
6774
method='POST',
6875
url=self.url + "/_session",
6976
headers=self.headers,
7077
json={
7178
'username': self.username,
7279
'password': self.password,
73-
}
80+
},
81+
cookies=self.jar,
82+
**self.http_config
7483
)
7584

7685
return response

‎test/integration/test_timeout.py‎

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# coding: utf-8
22

3-
# © Copyright IBM Corporation 2021.
3+
# © Copyright IBM Corporation 2021, 2025.
44
# #
55
# Licensed under the Apache License, Version 2.0 (the "License");
66
# you may not use this file except in compliance with the License.
@@ -86,8 +86,24 @@ def get_authenticate_arguments(req):
8686
return req.mock_calls[0]
8787

8888
@staticmethod
89-
def get_request_arguments(tci, srv, idx):
90-
tci.assertEqual(srv.http_client.request.call_count, idx + 1)
89+
def get_request_arguments(tci, srv, idx, call_count=None):
90+
"""
91+
Retrieve the arguments passed to a mocked HTTP request.
92+
93+
Parameters:
94+
tci (unittest.mock.Mock): A Mock object used for assertions.
95+
srv (Service): The instance of CloudantV1 service.
96+
idx (int): The index of the request in srv.http_client.request.mock_calls.
97+
call_count (int, optional): The total call count for the request. Defaults to None.
98+
99+
Returns:
100+
list: The arguments passed to the HTTP request at the specified index.
101+
102+
Raises:
103+
AssertionError: If the actual call count of the request does not match the provided or calculated call_count.
104+
"""
105+
call_count = idx + 1 if call_count is None else call_count
106+
tci.assertEqual(srv.http_client.request.call_count, call_count)
91107
return srv.http_client.request.mock_calls[idx]
92108

93109
# Every method tests an authenticator.
@@ -142,31 +158,27 @@ def test_timeout_cloudantv1_sessionauth(self):
142158
)
143159
my_service.set_service_url("http://cloudant.example")
144160

145-
# Mock out authentication
146-
orig_request = requests.request
147-
requests.request = Mock(return_value=Helpers.get_mocked_response())
148-
149161
# Mock out request response
150162
Helpers.mock_out_cloudant_request(my_service)
151163

152-
testcases = Helpers.defineTestCases(my_service)
164+
# Call the server
165+
my_service.get_server_information()
153166

154-
for tc_num, tc in enumerate(testcases):
155-
tc['set_timeout'](CUSTOM_TIMEOUT_CONFIG)
156-
157-
# Call the server
158-
my_service.get_server_information()
167+
# Assert timeout is set to the authenticator
168+
auth_args = Helpers.get_request_arguments(self, my_service.authenticator.token_manager, 0, call_count=2)
169+
self.assertEqual(auth_args.kwargs['timeout'], DEFAULT_TIMEOUT)
159170

160-
# Assert timeout is set to the authenticator
161-
auth_args = Helpers.get_authenticate_arguments(requests.request)
162-
Helpers.assert_default_timeout_setting(self, auth_args)
171+
# Assert timeout is set to the server request
172+
req_args = Helpers.get_request_arguments(self, my_service, 1, call_count=2)
173+
self.assertEqual(req_args.kwargs['timeout'], DEFAULT_TIMEOUT)
163174

164-
# Assert timeout is set in the server request
165-
req_args = Helpers.get_request_arguments(self, my_service, tc_num)
166-
tc['assert_func'](self, req_args)
175+
# Set a custom timeout and repeat the request. Client should be already authenticated.
176+
my_service.set_http_config(CUSTOM_TIMEOUT_CONFIG)
177+
my_service.get_server_information()
167178

168-
# Set back requests.request
169-
requests.request = orig_request
179+
# Assert the custom timeout is set to the server request
180+
req_args = Helpers.get_request_arguments(self, my_service, 2, call_count=3)
181+
self.assertEqual(req_args.kwargs['timeout'], CUSTOM_TIMEOUT)
170182

171183
def test_timeout_cloudantv1_iamauth(self):
172184
authenticator = IAMAuthenticator('apikey')

0 commit comments

Comments
 (0)