Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Changelog

* Requests sessions is initialized only once instead of it being done in each request.

## 4.41.0
* Add extra fields in `sender` and `receiver` details in `transfer` to `Transaction`
* Remove unused error code `AdjustmentAmountMustBeGreaterThanZero`
Expand Down
7 changes: 5 additions & 2 deletions braintree/configuration.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import requests
import braintree
from braintree.credentials_parser import CredentialsParser
from braintree.environment import Environment
Expand Down Expand Up @@ -101,8 +102,10 @@ def __init__(self, environment=None, merchant_id=None, public_key=None, private_
self.access_token = parser.access_token
self.timeout = kwargs.get("timeout", 60)
self.wrap_http_exceptions = kwargs.get("wrap_http_exceptions", False)

http_strategy = kwargs.get("http_strategy", None)
self.requests_session = requests.Session()
# Work around known proxy bug (see https://github.com/psf/requests/issues/5677)
self.requests_session.proxies.update(requests.utils.getproxies())

if http_strategy:
self._http_strategy = http_strategy(self, self.environment)
Expand All @@ -119,7 +122,7 @@ def graphql_base_url(self):
return self.environment.protocol + self.environment.graphql_server_and_port + "/graphql"

def http(self):
return braintree.util.http.Http(self)
return braintree.util.http.Http(self, requests_session=self.requests_session)

def graphql_client(self):
return GraphQLClient(self)
Expand Down
39 changes: 16 additions & 23 deletions braintree/util/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ def raise_exception_from_status(status, message=None):
else:
raise UnexpectedError("Unexpected HTTP_RESPONSE " + str(status))

def __init__(self, config, environment=None):
def __init__(self, config, environment=None, requests_session=None):
self.config = config
self.environment = environment or self.config.environment
self.requests_session = requests_session or requests.Session()

def post(self, path, params=None):
return self._make_request("POST", path, Http.ContentType.Xml, params)
Expand Down Expand Up @@ -100,35 +101,26 @@ def _make_request(self, http_verb, path, content_type, params=None, files=None,
return XmlUtil.dict_from_xml(response_body)

def http_do(self, http_verb, path, headers, request_body):
data = request_body
files = None
data, files = request_body, None
full_path = self.__full_path(path)

if type(request_body) is tuple:
data = request_body[0]
files = request_body[1]
if isinstance(request_body, tuple):
data, files = request_body[0], request_body[1]

if (self.config.environment == Environment.Development):
verify = False
else:
verify = self.environment.ssl_certificate

with requests.Session() as session:
request = requests.Request(
method=http_verb,
url=full_path,
headers=headers,
data=data,
files=files)
prepared_request = request.prepare()
prepared_request.url = full_path
# there's a bug in requests module that requires we manually update proxy settings,
# see https://github.com/psf/requests/issues/5677
session.proxies.update(requests.utils.getproxies())

response = session.send(prepared_request,
verify=verify,
timeout=self.config.timeout)
response = self.requests_session.request(
method=http_verb,
url=full_path,
headers=headers,
data=data,
files=files,
verify=verify,
timeout=self.config.timeout,
)

return [response.status_code, response.text]

Expand Down Expand Up @@ -174,7 +166,8 @@ def __headers(self, content_type, header_overrides=None):
if content_type == Http.ContentType.Xml:
headers["Content-type"] = Http.ContentType.Xml

headers.update(header_overrides or {})
if header_overrides is not None:
headers.update(header_overrides)

return headers

Expand Down
35 changes: 9 additions & 26 deletions tests/unit/test_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,33 +140,16 @@ def test_http_do_strategy(http_verb, path, headers, request_body):
http.handle_exception(requests.exceptions.ConnectTimeout())

def test_request_urls_retain_dots(self):
with patch('requests.Session.send') as send:
config = Configuration(
Environment.Development,
"integration_merchant_id",
public_key="integration_public_key",
private_key="integration_private_key",
wrap_http_exceptions=True
)
with patch.object(config.requests_session, 'request') as send:
send.return_value.status_code = 200
config = Configuration(
Environment.Development,
"integration_merchant_id",
public_key="integration_public_key",
private_key="integration_private_key",
wrap_http_exceptions=True
)
http = config.http()
http.get("/../../customers/")

prepared_request = send.call_args[0][0]
request_url = prepared_request.url
request_url = send.call_args[1]['url']
self.assertTrue(request_url.endswith("/../../customers/"))

def test_sessions_close_after_request(self):
with patch('requests.Session.send') as send, patch('requests.Session.close') as close:
send.return_value.status_code = 200
config = Configuration(
Environment.Development,
"integration_merchant_id",
public_key="integration_public_key",
private_key="integration_private_key",
wrap_http_exceptions=True
)
http = config.http()
http.get("/../../customers/")

self.assertTrue(close.called)