annotate urllib3/util/timeout.py @ 15:0a3943480712

planemo upload for repository https://toolrepo.galaxytrakr.org/view/jpayne/bioproject_to_srr_2/556cac4fb538
author jpayne
date Tue, 21 May 2024 01:05:30 -0400
parents 5eb2d5e3bf22
children
rev   line source
jpayne@7 1 from __future__ import annotations
jpayne@7 2
jpayne@7 3 import time
jpayne@7 4 import typing
jpayne@7 5 from enum import Enum
jpayne@7 6 from socket import getdefaulttimeout
jpayne@7 7
jpayne@7 8 from ..exceptions import TimeoutStateError
jpayne@7 9
jpayne@7 10 if typing.TYPE_CHECKING:
jpayne@7 11 from typing import Final
jpayne@7 12
jpayne@7 13
jpayne@7 14 class _TYPE_DEFAULT(Enum):
jpayne@7 15 # This value should never be passed to socket.settimeout() so for safety we use a -1.
jpayne@7 16 # socket.settimout() raises a ValueError for negative values.
jpayne@7 17 token = -1
jpayne@7 18
jpayne@7 19
jpayne@7 20 _DEFAULT_TIMEOUT: Final[_TYPE_DEFAULT] = _TYPE_DEFAULT.token
jpayne@7 21
jpayne@7 22 _TYPE_TIMEOUT = typing.Optional[typing.Union[float, _TYPE_DEFAULT]]
jpayne@7 23
jpayne@7 24
jpayne@7 25 class Timeout:
jpayne@7 26 """Timeout configuration.
jpayne@7 27
jpayne@7 28 Timeouts can be defined as a default for a pool:
jpayne@7 29
jpayne@7 30 .. code-block:: python
jpayne@7 31
jpayne@7 32 import urllib3
jpayne@7 33
jpayne@7 34 timeout = urllib3.util.Timeout(connect=2.0, read=7.0)
jpayne@7 35
jpayne@7 36 http = urllib3.PoolManager(timeout=timeout)
jpayne@7 37
jpayne@7 38 resp = http.request("GET", "https://example.com/")
jpayne@7 39
jpayne@7 40 print(resp.status)
jpayne@7 41
jpayne@7 42 Or per-request (which overrides the default for the pool):
jpayne@7 43
jpayne@7 44 .. code-block:: python
jpayne@7 45
jpayne@7 46 response = http.request("GET", "https://example.com/", timeout=Timeout(10))
jpayne@7 47
jpayne@7 48 Timeouts can be disabled by setting all the parameters to ``None``:
jpayne@7 49
jpayne@7 50 .. code-block:: python
jpayne@7 51
jpayne@7 52 no_timeout = Timeout(connect=None, read=None)
jpayne@7 53 response = http.request("GET", "https://example.com/", timeout=no_timeout)
jpayne@7 54
jpayne@7 55
jpayne@7 56 :param total:
jpayne@7 57 This combines the connect and read timeouts into one; the read timeout
jpayne@7 58 will be set to the time leftover from the connect attempt. In the
jpayne@7 59 event that both a connect timeout and a total are specified, or a read
jpayne@7 60 timeout and a total are specified, the shorter timeout will be applied.
jpayne@7 61
jpayne@7 62 Defaults to None.
jpayne@7 63
jpayne@7 64 :type total: int, float, or None
jpayne@7 65
jpayne@7 66 :param connect:
jpayne@7 67 The maximum amount of time (in seconds) to wait for a connection
jpayne@7 68 attempt to a server to succeed. Omitting the parameter will default the
jpayne@7 69 connect timeout to the system default, probably `the global default
jpayne@7 70 timeout in socket.py
jpayne@7 71 <http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_.
jpayne@7 72 None will set an infinite timeout for connection attempts.
jpayne@7 73
jpayne@7 74 :type connect: int, float, or None
jpayne@7 75
jpayne@7 76 :param read:
jpayne@7 77 The maximum amount of time (in seconds) to wait between consecutive
jpayne@7 78 read operations for a response from the server. Omitting the parameter
jpayne@7 79 will default the read timeout to the system default, probably `the
jpayne@7 80 global default timeout in socket.py
jpayne@7 81 <http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_.
jpayne@7 82 None will set an infinite timeout.
jpayne@7 83
jpayne@7 84 :type read: int, float, or None
jpayne@7 85
jpayne@7 86 .. note::
jpayne@7 87
jpayne@7 88 Many factors can affect the total amount of time for urllib3 to return
jpayne@7 89 an HTTP response.
jpayne@7 90
jpayne@7 91 For example, Python's DNS resolver does not obey the timeout specified
jpayne@7 92 on the socket. Other factors that can affect total request time include
jpayne@7 93 high CPU load, high swap, the program running at a low priority level,
jpayne@7 94 or other behaviors.
jpayne@7 95
jpayne@7 96 In addition, the read and total timeouts only measure the time between
jpayne@7 97 read operations on the socket connecting the client and the server,
jpayne@7 98 not the total amount of time for the request to return a complete
jpayne@7 99 response. For most requests, the timeout is raised because the server
jpayne@7 100 has not sent the first byte in the specified time. This is not always
jpayne@7 101 the case; if a server streams one byte every fifteen seconds, a timeout
jpayne@7 102 of 20 seconds will not trigger, even though the request will take
jpayne@7 103 several minutes to complete.
jpayne@7 104 """
jpayne@7 105
jpayne@7 106 #: A sentinel object representing the default timeout value
jpayne@7 107 DEFAULT_TIMEOUT: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT
jpayne@7 108
jpayne@7 109 def __init__(
jpayne@7 110 self,
jpayne@7 111 total: _TYPE_TIMEOUT = None,
jpayne@7 112 connect: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
jpayne@7 113 read: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
jpayne@7 114 ) -> None:
jpayne@7 115 self._connect = self._validate_timeout(connect, "connect")
jpayne@7 116 self._read = self._validate_timeout(read, "read")
jpayne@7 117 self.total = self._validate_timeout(total, "total")
jpayne@7 118 self._start_connect: float | None = None
jpayne@7 119
jpayne@7 120 def __repr__(self) -> str:
jpayne@7 121 return f"{type(self).__name__}(connect={self._connect!r}, read={self._read!r}, total={self.total!r})"
jpayne@7 122
jpayne@7 123 # __str__ provided for backwards compatibility
jpayne@7 124 __str__ = __repr__
jpayne@7 125
jpayne@7 126 @staticmethod
jpayne@7 127 def resolve_default_timeout(timeout: _TYPE_TIMEOUT) -> float | None:
jpayne@7 128 return getdefaulttimeout() if timeout is _DEFAULT_TIMEOUT else timeout
jpayne@7 129
jpayne@7 130 @classmethod
jpayne@7 131 def _validate_timeout(cls, value: _TYPE_TIMEOUT, name: str) -> _TYPE_TIMEOUT:
jpayne@7 132 """Check that a timeout attribute is valid.
jpayne@7 133
jpayne@7 134 :param value: The timeout value to validate
jpayne@7 135 :param name: The name of the timeout attribute to validate. This is
jpayne@7 136 used to specify in error messages.
jpayne@7 137 :return: The validated and casted version of the given value.
jpayne@7 138 :raises ValueError: If it is a numeric value less than or equal to
jpayne@7 139 zero, or the type is not an integer, float, or None.
jpayne@7 140 """
jpayne@7 141 if value is None or value is _DEFAULT_TIMEOUT:
jpayne@7 142 return value
jpayne@7 143
jpayne@7 144 if isinstance(value, bool):
jpayne@7 145 raise ValueError(
jpayne@7 146 "Timeout cannot be a boolean value. It must "
jpayne@7 147 "be an int, float or None."
jpayne@7 148 )
jpayne@7 149 try:
jpayne@7 150 float(value)
jpayne@7 151 except (TypeError, ValueError):
jpayne@7 152 raise ValueError(
jpayne@7 153 "Timeout value %s was %s, but it must be an "
jpayne@7 154 "int, float or None." % (name, value)
jpayne@7 155 ) from None
jpayne@7 156
jpayne@7 157 try:
jpayne@7 158 if value <= 0:
jpayne@7 159 raise ValueError(
jpayne@7 160 "Attempted to set %s timeout to %s, but the "
jpayne@7 161 "timeout cannot be set to a value less "
jpayne@7 162 "than or equal to 0." % (name, value)
jpayne@7 163 )
jpayne@7 164 except TypeError:
jpayne@7 165 raise ValueError(
jpayne@7 166 "Timeout value %s was %s, but it must be an "
jpayne@7 167 "int, float or None." % (name, value)
jpayne@7 168 ) from None
jpayne@7 169
jpayne@7 170 return value
jpayne@7 171
jpayne@7 172 @classmethod
jpayne@7 173 def from_float(cls, timeout: _TYPE_TIMEOUT) -> Timeout:
jpayne@7 174 """Create a new Timeout from a legacy timeout value.
jpayne@7 175
jpayne@7 176 The timeout value used by httplib.py sets the same timeout on the
jpayne@7 177 connect(), and recv() socket requests. This creates a :class:`Timeout`
jpayne@7 178 object that sets the individual timeouts to the ``timeout`` value
jpayne@7 179 passed to this function.
jpayne@7 180
jpayne@7 181 :param timeout: The legacy timeout value.
jpayne@7 182 :type timeout: integer, float, :attr:`urllib3.util.Timeout.DEFAULT_TIMEOUT`, or None
jpayne@7 183 :return: Timeout object
jpayne@7 184 :rtype: :class:`Timeout`
jpayne@7 185 """
jpayne@7 186 return Timeout(read=timeout, connect=timeout)
jpayne@7 187
jpayne@7 188 def clone(self) -> Timeout:
jpayne@7 189 """Create a copy of the timeout object
jpayne@7 190
jpayne@7 191 Timeout properties are stored per-pool but each request needs a fresh
jpayne@7 192 Timeout object to ensure each one has its own start/stop configured.
jpayne@7 193
jpayne@7 194 :return: a copy of the timeout object
jpayne@7 195 :rtype: :class:`Timeout`
jpayne@7 196 """
jpayne@7 197 # We can't use copy.deepcopy because that will also create a new object
jpayne@7 198 # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to
jpayne@7 199 # detect the user default.
jpayne@7 200 return Timeout(connect=self._connect, read=self._read, total=self.total)
jpayne@7 201
jpayne@7 202 def start_connect(self) -> float:
jpayne@7 203 """Start the timeout clock, used during a connect() attempt
jpayne@7 204
jpayne@7 205 :raises urllib3.exceptions.TimeoutStateError: if you attempt
jpayne@7 206 to start a timer that has been started already.
jpayne@7 207 """
jpayne@7 208 if self._start_connect is not None:
jpayne@7 209 raise TimeoutStateError("Timeout timer has already been started.")
jpayne@7 210 self._start_connect = time.monotonic()
jpayne@7 211 return self._start_connect
jpayne@7 212
jpayne@7 213 def get_connect_duration(self) -> float:
jpayne@7 214 """Gets the time elapsed since the call to :meth:`start_connect`.
jpayne@7 215
jpayne@7 216 :return: Elapsed time in seconds.
jpayne@7 217 :rtype: float
jpayne@7 218 :raises urllib3.exceptions.TimeoutStateError: if you attempt
jpayne@7 219 to get duration for a timer that hasn't been started.
jpayne@7 220 """
jpayne@7 221 if self._start_connect is None:
jpayne@7 222 raise TimeoutStateError(
jpayne@7 223 "Can't get connect duration for timer that has not started."
jpayne@7 224 )
jpayne@7 225 return time.monotonic() - self._start_connect
jpayne@7 226
jpayne@7 227 @property
jpayne@7 228 def connect_timeout(self) -> _TYPE_TIMEOUT:
jpayne@7 229 """Get the value to use when setting a connection timeout.
jpayne@7 230
jpayne@7 231 This will be a positive float or integer, the value None
jpayne@7 232 (never timeout), or the default system timeout.
jpayne@7 233
jpayne@7 234 :return: Connect timeout.
jpayne@7 235 :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None
jpayne@7 236 """
jpayne@7 237 if self.total is None:
jpayne@7 238 return self._connect
jpayne@7 239
jpayne@7 240 if self._connect is None or self._connect is _DEFAULT_TIMEOUT:
jpayne@7 241 return self.total
jpayne@7 242
jpayne@7 243 return min(self._connect, self.total) # type: ignore[type-var]
jpayne@7 244
jpayne@7 245 @property
jpayne@7 246 def read_timeout(self) -> float | None:
jpayne@7 247 """Get the value for the read timeout.
jpayne@7 248
jpayne@7 249 This assumes some time has elapsed in the connection timeout and
jpayne@7 250 computes the read timeout appropriately.
jpayne@7 251
jpayne@7 252 If self.total is set, the read timeout is dependent on the amount of
jpayne@7 253 time taken by the connect timeout. If the connection time has not been
jpayne@7 254 established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be
jpayne@7 255 raised.
jpayne@7 256
jpayne@7 257 :return: Value to use for the read timeout.
jpayne@7 258 :rtype: int, float or None
jpayne@7 259 :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect`
jpayne@7 260 has not yet been called on this object.
jpayne@7 261 """
jpayne@7 262 if (
jpayne@7 263 self.total is not None
jpayne@7 264 and self.total is not _DEFAULT_TIMEOUT
jpayne@7 265 and self._read is not None
jpayne@7 266 and self._read is not _DEFAULT_TIMEOUT
jpayne@7 267 ):
jpayne@7 268 # In case the connect timeout has not yet been established.
jpayne@7 269 if self._start_connect is None:
jpayne@7 270 return self._read
jpayne@7 271 return max(0, min(self.total - self.get_connect_duration(), self._read))
jpayne@7 272 elif self.total is not None and self.total is not _DEFAULT_TIMEOUT:
jpayne@7 273 return max(0, self.total - self.get_connect_duration())
jpayne@7 274 else:
jpayne@7 275 return self.resolve_default_timeout(self._read)