Spaces:
Sleeping
Sleeping
from __future__ import absolute_import | |
import time | |
# The default socket timeout, used by httplib to indicate that no timeout was | |
# specified by the user | |
from socket import _GLOBAL_DEFAULT_TIMEOUT | |
from ..exceptions import TimeoutStateError | |
# A sentinel value to indicate that no timeout was specified by the user in | |
# urllib3 | |
_Default = object() | |
# Use time.monotonic if available. | |
current_time = getattr(time, "monotonic", time.time) | |
class Timeout(object): | |
"""Timeout configuration. | |
Timeouts can be defined as a default for a pool: | |
.. code-block:: python | |
timeout = Timeout(connect=2.0, read=7.0) | |
http = PoolManager(timeout=timeout) | |
response = http.request('GET', 'http://example.com/') | |
Or per-request (which overrides the default for the pool): | |
.. code-block:: python | |
response = http.request('GET', 'http://example.com/', timeout=Timeout(10)) | |
Timeouts can be disabled by setting all the parameters to ``None``: | |
.. code-block:: python | |
no_timeout = Timeout(connect=None, read=None) | |
response = http.request('GET', 'http://example.com/, timeout=no_timeout) | |
:param total: | |
This combines the connect and read timeouts into one; the read timeout | |
will be set to the time leftover from the connect attempt. In the | |
event that both a connect timeout and a total are specified, or a read | |
timeout and a total are specified, the shorter timeout will be applied. | |
Defaults to None. | |
:type total: int, float, or None | |
:param connect: | |
The maximum amount of time (in seconds) to wait for a connection | |
attempt to a server to succeed. Omitting the parameter will default the | |
connect timeout to the system default, probably `the global default | |
timeout in socket.py | |
<http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. | |
None will set an infinite timeout for connection attempts. | |
:type connect: int, float, or None | |
:param read: | |
The maximum amount of time (in seconds) to wait between consecutive | |
read operations for a response from the server. Omitting the parameter | |
will default the read timeout to the system default, probably `the | |
global default timeout in socket.py | |
<http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. | |
None will set an infinite timeout. | |
:type read: int, float, or None | |
.. note:: | |
Many factors can affect the total amount of time for urllib3 to return | |
an HTTP response. | |
For example, Python's DNS resolver does not obey the timeout specified | |
on the socket. Other factors that can affect total request time include | |
high CPU load, high swap, the program running at a low priority level, | |
or other behaviors. | |
In addition, the read and total timeouts only measure the time between | |
read operations on the socket connecting the client and the server, | |
not the total amount of time for the request to return a complete | |
response. For most requests, the timeout is raised because the server | |
has not sent the first byte in the specified time. This is not always | |
the case; if a server streams one byte every fifteen seconds, a timeout | |
of 20 seconds will not trigger, even though the request will take | |
several minutes to complete. | |
If your goal is to cut off any request after a set amount of wall clock | |
time, consider having a second "watcher" thread to cut off a slow | |
request. | |
""" | |
#: A sentinel object representing the default timeout value | |
DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT | |
def __init__(self, total=None, connect=_Default, read=_Default): | |
self._connect = self._validate_timeout(connect, "connect") | |
self._read = self._validate_timeout(read, "read") | |
self.total = self._validate_timeout(total, "total") | |
self._start_connect = None | |
def __repr__(self): | |
return "%s(connect=%r, read=%r, total=%r)" % ( | |
type(self).__name__, | |
self._connect, | |
self._read, | |
self.total, | |
) | |
# __str__ provided for backwards compatibility | |
__str__ = __repr__ | |
def _validate_timeout(cls, value, name): | |
"""Check that a timeout attribute is valid. | |
:param value: The timeout value to validate | |
:param name: The name of the timeout attribute to validate. This is | |
used to specify in error messages. | |
:return: The validated and casted version of the given value. | |
:raises ValueError: If it is a numeric value less than or equal to | |
zero, or the type is not an integer, float, or None. | |
""" | |
if value is _Default: | |
return cls.DEFAULT_TIMEOUT | |
if value is None or value is cls.DEFAULT_TIMEOUT: | |
return value | |
if isinstance(value, bool): | |
raise ValueError( | |
"Timeout cannot be a boolean value. It must " | |
"be an int, float or None." | |
) | |
try: | |
float(value) | |
except (TypeError, ValueError): | |
raise ValueError( | |
"Timeout value %s was %s, but it must be an " | |
"int, float or None." % (name, value) | |
) | |
try: | |
if value <= 0: | |
raise ValueError( | |
"Attempted to set %s timeout to %s, but the " | |
"timeout cannot be set to a value less " | |
"than or equal to 0." % (name, value) | |
) | |
except TypeError: | |
# Python 3 | |
raise ValueError( | |
"Timeout value %s was %s, but it must be an " | |
"int, float or None." % (name, value) | |
) | |
return value | |
def from_float(cls, timeout): | |
"""Create a new Timeout from a legacy timeout value. | |
The timeout value used by httplib.py sets the same timeout on the | |
connect(), and recv() socket requests. This creates a :class:`Timeout` | |
object that sets the individual timeouts to the ``timeout`` value | |
passed to this function. | |
:param timeout: The legacy timeout value. | |
:type timeout: integer, float, sentinel default object, or None | |
:return: Timeout object | |
:rtype: :class:`Timeout` | |
""" | |
return Timeout(read=timeout, connect=timeout) | |
def clone(self): | |
"""Create a copy of the timeout object | |
Timeout properties are stored per-pool but each request needs a fresh | |
Timeout object to ensure each one has its own start/stop configured. | |
:return: a copy of the timeout object | |
:rtype: :class:`Timeout` | |
""" | |
# We can't use copy.deepcopy because that will also create a new object | |
# for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to | |
# detect the user default. | |
return Timeout(connect=self._connect, read=self._read, total=self.total) | |
def start_connect(self): | |
"""Start the timeout clock, used during a connect() attempt | |
:raises urllib3.exceptions.TimeoutStateError: if you attempt | |
to start a timer that has been started already. | |
""" | |
if self._start_connect is not None: | |
raise TimeoutStateError("Timeout timer has already been started.") | |
self._start_connect = current_time() | |
return self._start_connect | |
def get_connect_duration(self): | |
"""Gets the time elapsed since the call to :meth:`start_connect`. | |
:return: Elapsed time in seconds. | |
:rtype: float | |
:raises urllib3.exceptions.TimeoutStateError: if you attempt | |
to get duration for a timer that hasn't been started. | |
""" | |
if self._start_connect is None: | |
raise TimeoutStateError( | |
"Can't get connect duration for timer that has not started." | |
) | |
return current_time() - self._start_connect | |
def connect_timeout(self): | |
"""Get the value to use when setting a connection timeout. | |
This will be a positive float or integer, the value None | |
(never timeout), or the default system timeout. | |
:return: Connect timeout. | |
:rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None | |
""" | |
if self.total is None: | |
return self._connect | |
if self._connect is None or self._connect is self.DEFAULT_TIMEOUT: | |
return self.total | |
return min(self._connect, self.total) | |
def read_timeout(self): | |
"""Get the value for the read timeout. | |
This assumes some time has elapsed in the connection timeout and | |
computes the read timeout appropriately. | |
If self.total is set, the read timeout is dependent on the amount of | |
time taken by the connect timeout. If the connection time has not been | |
established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be | |
raised. | |
:return: Value to use for the read timeout. | |
:rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None | |
:raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` | |
has not yet been called on this object. | |
""" | |
if ( | |
self.total is not None | |
and self.total is not self.DEFAULT_TIMEOUT | |
and self._read is not None | |
and self._read is not self.DEFAULT_TIMEOUT | |
): | |
# In case the connect timeout has not yet been established. | |
if self._start_connect is None: | |
return self._read | |
return max(0, min(self.total - self.get_connect_duration(), self._read)) | |
elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: | |
return max(0, self.total - self.get_connect_duration()) | |
else: | |
return self._read | |