Skip to content

Commit

Permalink
Expose close() method in botocore.client instances
Browse files Browse the repository at this point in the history
- Enable pooled/keep-alive connections to be closed explicitly
- Add tests for client, endpoint & session close()
  • Loading branch information
vtermanis authored and nateprewitt committed Jun 9, 2022
1 parent df7662b commit 4efc6e3
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 0 deletions.
4 changes: 4 additions & 0 deletions botocore/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,10 @@ def __getattr__(self, item):
f"'{self.__class__.__name__}' object has no attribute '{item}'"
)

def close(self):
"""Closes underlying endpoint connections."""
self._endpoint.close()

def _register_handlers(self):
# Register the handler required to sign requests.
service_id = self.meta.service_model.service_id.hyphenize()
Expand Down
3 changes: 3 additions & 0 deletions botocore/endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ def __init__(
def __repr__(self):
return f'{self._endpoint_prefix}({self.host})'

def close(self):
self.http_session.close()

def make_request(self, operation_model, request_dict):
logger.debug(
"Making request for %s with params: %s",
Expand Down
5 changes: 5 additions & 0 deletions botocore/httpsession.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,11 @@ def _chunked(self, headers):
transfer_encoding = ensure_bytes(transfer_encoding)
return transfer_encoding.lower() == b'chunked'

def close(self):
self._manager.clear()
for manager in self._proxy_managers.values():
manager.clear()

def send(self, request):
try:
proxy_url = self._proxy_config.proxy_url_for(request.url)
Expand Down
9 changes: 9 additions & 0 deletions tests/unit/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1585,6 +1585,15 @@ def test_client_operation_hostname_binding_configuration(self):
request_dict = self.endpoint.make_request.call_args[0][1]
self.assertEqual(request_dict['url'], expected_url)

def test_client_close(self):
creator = self.create_client_creator()
service_client = creator.create_client(
'myservice', 'us-west-2', credentials=self.credentials
)

service_client.close()
self.endpoint.close.assert_called_once_with()


class TestClientErrors(TestAutoGeneratedClient):
def add_error_response(self, error_response):
Expand Down
4 changes: 4 additions & 0 deletions tests/unit/test_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ def test_parses_modeled_exception_fields(self):
}
self.assertEqual(response, expected_response)

def test_close(self):
self.endpoint.close()
self.endpoint.http_session.close.assert_called_once_with()


class TestRetryInterface(TestEndpointBase):
def setUp(self):
Expand Down
20 changes: 20 additions & 0 deletions tests/unit/test_http_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,3 +496,23 @@ def test_chunked_encoding_is_not_set_without_header(self):

session.send(self.request.prepare())
self.assert_request_sent(chunked=False)

def test_close(self):
session = URLLib3Session()
session.close()
self.pool_manager.clear.assert_called_once_with()

def test_close_proxied(self):
proxies = {'https': 'http://proxy.com', 'http': 'http://proxy2.com'}
session = URLLib3Session(proxies=proxies)
for proxy, proxy_url in proxies.items():
self.request.url = '%s://example.com/' % proxy
session.send(self.request.prepare())

session.close()
self.proxy_manager_fun.return_value.clear.assert_called_with()
# One call for pool manager, one call for each of the proxies
self.assertEqual(
self.proxy_manager_fun.return_value.clear.call_count,
1 + len(proxies),
)

0 comments on commit 4efc6e3

Please sign in to comment.