You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			276 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			276 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
from __future__ import annotations
 | 
						|
 | 
						|
import time
 | 
						|
import typing
 | 
						|
from enum import Enum
 | 
						|
from socket import getdefaulttimeout
 | 
						|
 | 
						|
from ..exceptions import TimeoutStateError
 | 
						|
 | 
						|
if typing.TYPE_CHECKING:
 | 
						|
    from typing import Final
 | 
						|
 | 
						|
 | 
						|
class _TYPE_DEFAULT(Enum):
 | 
						|
    # This value should never be passed to socket.settimeout() so for safety we use a -1.
 | 
						|
    # socket.settimout() raises a ValueError for negative values.
 | 
						|
    token = -1
 | 
						|
 | 
						|
 | 
						|
_DEFAULT_TIMEOUT: Final[_TYPE_DEFAULT] = _TYPE_DEFAULT.token
 | 
						|
 | 
						|
_TYPE_TIMEOUT = typing.Optional[typing.Union[float, _TYPE_DEFAULT]]
 | 
						|
 | 
						|
 | 
						|
class Timeout:
 | 
						|
    """Timeout configuration.
 | 
						|
 | 
						|
    Timeouts can be defined as a default for a pool:
 | 
						|
 | 
						|
    .. code-block:: python
 | 
						|
 | 
						|
        import urllib3
 | 
						|
 | 
						|
        timeout = urllib3.util.Timeout(connect=2.0, read=7.0)
 | 
						|
 | 
						|
        http = urllib3.PoolManager(timeout=timeout)
 | 
						|
 | 
						|
        resp = http.request("GET", "https://example.com/")
 | 
						|
 | 
						|
        print(resp.status)
 | 
						|
 | 
						|
    Or per-request (which overrides the default for the pool):
 | 
						|
 | 
						|
    .. code-block:: python
 | 
						|
 | 
						|
       response = http.request("GET", "https://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", "https://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.
 | 
						|
    """
 | 
						|
 | 
						|
    #: A sentinel object representing the default timeout value
 | 
						|
    DEFAULT_TIMEOUT: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        total: _TYPE_TIMEOUT = None,
 | 
						|
        connect: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
 | 
						|
        read: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
 | 
						|
    ) -> None:
 | 
						|
        self._connect = self._validate_timeout(connect, "connect")
 | 
						|
        self._read = self._validate_timeout(read, "read")
 | 
						|
        self.total = self._validate_timeout(total, "total")
 | 
						|
        self._start_connect: float | None = None
 | 
						|
 | 
						|
    def __repr__(self) -> str:
 | 
						|
        return f"{type(self).__name__}(connect={self._connect!r}, read={self._read!r}, total={self.total!r})"
 | 
						|
 | 
						|
    # __str__ provided for backwards compatibility
 | 
						|
    __str__ = __repr__
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def resolve_default_timeout(timeout: _TYPE_TIMEOUT) -> float | None:
 | 
						|
        return getdefaulttimeout() if timeout is _DEFAULT_TIMEOUT else timeout
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def _validate_timeout(cls, value: _TYPE_TIMEOUT, name: str) -> _TYPE_TIMEOUT:
 | 
						|
        """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 None or value is _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)
 | 
						|
            ) from None
 | 
						|
 | 
						|
        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:
 | 
						|
            raise ValueError(
 | 
						|
                "Timeout value %s was %s, but it must be an "
 | 
						|
                "int, float or None." % (name, value)
 | 
						|
            ) from None
 | 
						|
 | 
						|
        return value
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def from_float(cls, timeout: _TYPE_TIMEOUT) -> 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, :attr:`urllib3.util.Timeout.DEFAULT_TIMEOUT`, or None
 | 
						|
        :return: Timeout object
 | 
						|
        :rtype: :class:`Timeout`
 | 
						|
        """
 | 
						|
        return Timeout(read=timeout, connect=timeout)
 | 
						|
 | 
						|
    def clone(self) -> Timeout:
 | 
						|
        """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) -> float:
 | 
						|
        """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 = time.monotonic()
 | 
						|
        return self._start_connect
 | 
						|
 | 
						|
    def get_connect_duration(self) -> float:
 | 
						|
        """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 time.monotonic() - self._start_connect
 | 
						|
 | 
						|
    @property
 | 
						|
    def connect_timeout(self) -> _TYPE_TIMEOUT:
 | 
						|
        """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 _DEFAULT_TIMEOUT:
 | 
						|
            return self.total
 | 
						|
 | 
						|
        return min(self._connect, self.total)  # type: ignore[type-var]
 | 
						|
 | 
						|
    @property
 | 
						|
    def read_timeout(self) -> float | None:
 | 
						|
        """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 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 _DEFAULT_TIMEOUT
 | 
						|
            and self._read is not None
 | 
						|
            and self._read is not _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 _DEFAULT_TIMEOUT:
 | 
						|
            return max(0, self.total - self.get_connect_duration())
 | 
						|
        else:
 | 
						|
            return self.resolve_default_timeout(self._read)
 |