Skip to content

Commit

Permalink
Add support for per invocation timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
phillbaker committed Dec 18, 2019
1 parent 5432496 commit defe3ec
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 9 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Release notes
=============

version 0.8.4 (unreleased)
------------------------
* Add per invocation timeout


version 0.8.3 (2019-06-24)
------------------------
* Fix pypi description format

version 0.8.2 (2019-06-23)
------------------------
* Add option to disable the sorting of namespaces.
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ They are as follows:
* doctor:: The schema `doctor` specifies an object used to fix broken schema(s).
* xstq:: The XML schema type qualified flag indicates that `xsi:type` attribute __values__ should be qualified by namespace.
* prefixes:: Elements of the soap message should be qualified (when needed) using XML prefixes as opposed to xmlns=\"\" syntax.
* timeout:: The URL connection timeout (seconds) default=90.
* timeout:: The URL connection timeout (seconds) default=90. A timeout can also be set per method invocation.
* retxml:: Flag that causes the I{raw} soap envelope to be returned instead of the python object graph.
* autoblend:: Flag that ensures that the schema(s) defined
within the WSDL import each other.
Expand Down Expand Up @@ -1062,6 +1062,17 @@ custom_https = ClientHttpsTransport('/path/to/certificate_file', '/path/to/key_f
client = Client(url, transport=custom_https),
```

## Timeouts

Per request timeouts can be set by using a `__timeout` keyword argument in
each call. This supersedes the global client default. For example, the
following call will have a timeout of 10 seconds:


```python
client = Client(url, timeout=30)
client.service.test(__timeout=10)
```

## Performance

Expand Down
9 changes: 6 additions & 3 deletions suds/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,8 @@ class _SoapClient:
"""

TIMEOUT_ARGUMENT = "__timeout"

def __init__(self, client, method):
"""
@param client: A suds client.
Expand Down Expand Up @@ -697,17 +699,18 @@ def invoke(self, args, kwargs):
timer = metrics.Timer()
timer.start()
binding = self.method.binding.input
timeout = kwargs.pop(_SoapClient.TIMEOUT_ARGUMENT, None)
soapenv = binding.get_message(self.method, args, kwargs)
timer.stop()
method_name = self.method.name
metrics.log.debug("message for '%s' created: %s", method_name, timer)
timer.start()
result = self.send(soapenv)
result = self.send(soapenv, timeout=timeout)
timer.stop()
metrics.log.debug("method '%s' invoked: %s", method_name, timer)
return result

def send(self, soapenv):
def send(self, soapenv, timeout=None):
"""
Send SOAP message.
Expand Down Expand Up @@ -739,7 +742,7 @@ def send(self, soapenv):
soapenv = ctx.envelope
if self.options.nosend:
return RequestContext(self.process_reply, soapenv)
request = suds.transport.Request(location, soapenv)
request = suds.transport.Request(location, soapenv, timeout)
request.headers = self.__headers()
try:
timer = metrics.Timer()
Expand Down
4 changes: 3 additions & 1 deletion suds/transport/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,13 @@ class Request(UnicodeMixin):
@type url: str
@ivar message: The optional message to be sent in the request body.
@type message: bytes|None
@type timeout: int|None
@ivar headers: The HTTP headers to be used for the request.
@type headers: dict
"""

def __init__(self, url, message=None):
def __init__(self, url, message=None, timeout=None):
"""
Raised exception in case of detected non-ASCII URL characters may be
either UnicodeEncodeError or UnicodeDecodeError, depending on the used
Expand All @@ -64,6 +65,7 @@ def __init__(self, url, message=None):
self.__set_URL(url)
self.headers = {}
self.message = message
self.timeout = timeout

def __unicode__(self):
result = [u"URL: %s\nHEADERS: %s" % (self.url, self.headers)]
Expand Down
6 changes: 3 additions & 3 deletions suds/transport/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def send(self, request):
self.proxy = self.options.proxy
request.headers.update(u2request.headers)
log.debug('sending:\n%s', request)
fp = self.u2open(u2request)
fp = self.u2open(u2request, timeout=request.timeout)
self.getcookies(fp, u2request)
headers = fp.headers
if sys.version_info < (3, 0):
Expand Down Expand Up @@ -110,7 +110,7 @@ def getcookies(self, fp, u2request):
"""
self.cookiejar.extract_cookies(fp, u2request)

def u2open(self, u2request):
def u2open(self, u2request, timeout=None):
"""
Open a connection.
Expand All @@ -120,7 +120,7 @@ def u2open(self, u2request):
@rtype: fp
"""
tm = self.options.timeout
tm = timeout or self.options.timeout
url = self.u2opener()
if (sys.version_info < (3, 0)) and (self.u2ver() < 2.6):
socket.setdefaulttimeout(tm)
Expand Down
4 changes: 3 additions & 1 deletion tests/test_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,12 @@ def test_construct(self, message):
# Always use the same URL as different ways to specify a Request's URL
# are tested separately.
url = "some://url"
request = Request(url, message)
timeout = 10
request = Request(url, message, timeout)
assert request.url is url
assert request.message is message
assert request.headers == {}
assert request.timeout == timeout

def test_construct_with_no_message(self):
request = Request("some://url")
Expand Down
37 changes: 37 additions & 0 deletions tests/test_transport_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import base64
import re
import sys
from email.message import Message

# We can not use six.moves modules for this since we want to monkey-patch the
# exact underlying urllib2/urllib.request module in our tests and not just
Expand Down Expand Up @@ -597,6 +598,42 @@ def test_send_transforming_HTTPError_exceptions__accepted_no_content(self,
t.urlopener = MockURLOpenerSaboteur(open_exception=e_original)
assert t.send(create_request()) is None

def test_specify_timeout(self):
"""
HttpTransport send() operation should pass a Request timeout parameter
to urllib
"""
t = suds.transport.http.HttpTransport()
request = create_request()
request.timeout = 10

# Python 2 compatible object
class CompatibleHeaders(dict):
dict = {}

class MockResponse:
def info(self):
message = Message()
# Python 2 compatible response
message.getheaders = lambda k: {}
return message

@property
def headers(self):
return CompatibleHeaders()

def read(self):
return ''

class MockURLOpener:
def open(self, urllib_request, timeout=None):
assert timeout == request.timeout
return MockResponse()

t.urlopener = MockURLOpener()
t.send(request)


@pytest.mark.parametrize("url", test_URL_data)
def test_urlopener_default(self, url, send_method, monkeypatch):
"""
Expand Down

0 comments on commit defe3ec

Please sign in to comment.