Пример #1
0
    def test_history(self):
        retry = Retry(total=10, method_whitelist=frozenset(["GET", "POST"]))
        assert retry.history == tuple()
        connection_error = ConnectTimeoutError("conntimeout")
        retry = retry.increment("GET", "/test1", None, connection_error)
        history = (RequestHistory("GET", "/test1", connection_error, None,
                                  None), )
        assert retry.history == history

        read_error = ReadTimeoutError(None, "/test2", "read timed out")
        retry = retry.increment("POST", "/test2", None, read_error)
        history = (
            RequestHistory("GET", "/test1", connection_error, None, None),
            RequestHistory("POST", "/test2", read_error, None, None),
        )
        assert retry.history == history

        response = HTTPResponse(status=500)
        retry = retry.increment("GET", "/test3", response, None)
        history = (
            RequestHistory("GET", "/test1", connection_error, None, None),
            RequestHistory("POST", "/test2", read_error, None, None),
            RequestHistory("GET", "/test3", None, 500, None),
        )
        assert retry.history == history
Пример #2
0
 def test_retry_read_zero(self):
     """ No second chances on read timeouts, by default """
     error = ReadTimeoutError(None, "/", "read timed out")
     retry = Retry(read=0)
     with pytest.raises(MaxRetryError) as e:
         retry.increment(method="GET", error=error)
     assert e.value.reason == error
Пример #3
0
    def test_redirect_cross_host_set_removed_headers(self):
        with PoolManager() as http:
            r = http.request(
                "GET",
                "%s/redirect" % self.base_url,
                fields={"target": "%s/headers" % self.base_url_alt},
                headers={"X-API-Secret": "foo", "Authorization": "bar"},
                retries=Retry(remove_headers_on_redirect=["X-API-Secret"]),
            )

            assert r.status == 200

            data = json.loads(r.data.decode("utf-8"))

            assert "X-API-Secret" not in data
            assert data["Authorization"] == "bar"

            r = http.request(
                "GET",
                "%s/redirect" % self.base_url,
                fields={"target": "%s/headers" % self.base_url_alt},
                headers={"x-api-secret": "foo", "authorization": "bar"},
                retries=Retry(remove_headers_on_redirect=["X-API-Secret"]),
            )

            assert r.status == 200

            data = json.loads(r.data.decode("utf-8"))

            assert "x-api-secret" not in data
            assert "X-API-Secret" not in data
            assert data["Authorization"] == "bar"
Пример #4
0
    def test_raise_on_status(self):
        with PoolManager() as http:
            with pytest.raises(MaxRetryError):
                # the default is to raise
                r = http.request(
                    "GET",
                    "%s/status" % self.base_url,
                    fields={"status": "500 Internal Server Error"},
                    retries=Retry(total=1, status_forcelist=range(500, 600)),
                )

            with pytest.raises(MaxRetryError):
                # raise explicitly
                r = http.request(
                    "GET",
                    "%s/status" % self.base_url,
                    fields={"status": "500 Internal Server Error"},
                    retries=Retry(
                        total=1, status_forcelist=range(500, 600), raise_on_status=True
                    ),
                )

            # don't raise
            r = http.request(
                "GET",
                "%s/status" % self.base_url,
                fields={"status": "500 Internal Server Error"},
                retries=Retry(
                    total=1, status_forcelist=range(500, 600), raise_on_status=False
                ),
            )

            assert r.status == 500
Пример #5
0
 def test_retry_higher_total_loses_vs_read(self):
     """ A lower read timeout than the total is honored """
     error = ReadTimeoutError(None, "/", "read timed out")
     retry = Retry(read=2, total=3)
     retry = retry.increment(method="GET", error=error)
     retry = retry.increment(method="GET", error=error)
     with pytest.raises(MaxRetryError):
         retry.increment(method="GET", error=error)
Пример #6
0
 def test_retry_higher_total_loses(self):
     """ A lower connect timeout than the total is honored """
     error = ConnectTimeoutError()
     retry = Retry(connect=2, total=3)
     retry = retry.increment(error=error)
     retry = retry.increment(error=error)
     with pytest.raises(MaxRetryError):
         retry.increment(error=error)
Пример #7
0
 def test_retry_both_specified(self):
     """Total can win if it's lower than the connect value"""
     error = ConnectTimeoutError()
     retry = Retry(connect=3, total=2)
     retry = retry.increment(error=error)
     retry = retry.increment(error=error)
     with pytest.raises(MaxRetryError) as e:
         retry.increment(error=error)
     assert e.value.reason == error
Пример #8
0
 def test_status_counter(self):
     resp = HTTPResponse(status=400)
     retry = Retry(status=2)
     retry = retry.increment(response=resp)
     retry = retry.increment(response=resp)
     with pytest.raises(MaxRetryError) as e:
         retry.increment(response=resp)
     assert str(e.value.reason) == ResponseError.SPECIFIC_ERROR.format(
         status_code=400)
Пример #9
0
 def test_string(self):
     """ Retry string representation looks the way we expect """
     retry = Retry()
     assert (
         str(retry) ==
         "Retry(total=10, connect=None, read=None, redirect=None, status=None)"
     )
     for _ in range(3):
         retry = retry.increment(method="GET")
     assert (
         str(retry) ==
         "Retry(total=7, connect=None, read=None, redirect=None, status=None)"
     )
Пример #10
0
 def test_retries(self):
     fp = BytesIO(b"")
     resp = HTTPResponse(fp)
     assert resp.retries is None
     retry = Retry()
     resp = HTTPResponse(fp, retries=retry)
     assert resp.retries == retry
Пример #11
0
    def test_retry_total_none(self):
        """ if Total is none, connect error should take precedence """
        error = ConnectTimeoutError()
        retry = Retry(connect=2, total=None)
        retry = retry.increment(error=error)
        retry = retry.increment(error=error)
        with pytest.raises(MaxRetryError) as e:
            retry.increment(error=error)
        assert e.value.reason == error

        error = ReadTimeoutError(None, "/", "read timed out")
        retry = Retry(connect=2, total=None)
        retry = retry.increment(method="GET", error=error)
        retry = retry.increment(method="GET", error=error)
        retry = retry.increment(method="GET", error=error)
        assert not retry.is_exhausted()
Пример #12
0
    def test_preserve_chunked_on_broken_connection(self):
        self.chunked_requests = 0

        def socket_handler(listener):
            for i in range(2):
                sock = listener.accept()[0]
                request = consume_socket(sock)
                if b"transfer-encoding: chunked" in request.split(b"\r\n"):
                    self.chunked_requests += 1

                if i == 0:
                    # Bad HTTP version will trigger a connection close
                    sock.send(b"HTTP/0.5 200 OK\r\n\r\n")
                else:
                    sock.send(b"HTTP/1.1 200 OK\r\n\r\n")
                sock.close()

        self._start_server(socket_handler)
        with HTTPConnectionPool(self.host, self.port) as pool:
            retries = Retry(read=1)
            pool.urlopen(
                "GET",
                "/",
                body=iter([b"chunk1", b"chunk2"]),
                preload_content=False,
                retries=retries,
            )
        assert self.chunked_requests == 2
Пример #13
0
    def test_connect_timeout(self):
        url = "/"
        host, port = TARPIT_HOST, 80
        timeout = Timeout(connect=SHORT_TIMEOUT)

        # Pool-global timeout
        with HTTPConnectionPool(host, port, timeout=timeout) as pool:
            conn = pool._get_conn()
            with pytest.raises(ConnectTimeoutError):
                pool._make_request(conn, "GET", url)

            # Retries
            retries = Retry(connect=0)
            with pytest.raises(MaxRetryError):
                pool.request("GET", url, retries=retries)

        # Request-specific connection timeouts
        big_timeout = Timeout(read=LONG_TIMEOUT, connect=LONG_TIMEOUT)
        with HTTPConnectionPool(host, port, timeout=big_timeout,
                                retries=False) as pool:
            conn = pool._get_conn()
            with pytest.raises(ConnectTimeoutError):
                pool._make_request(conn, "GET", url, timeout=timeout)

            pool._put_conn(conn)
            with pytest.raises(ConnectTimeoutError):
                pool.request("GET", url, timeout=timeout)
Пример #14
0
    def test_disabled_retry(self):
        """ Disabled retries should disable redirect handling. """
        with PoolManager() as http:
            r = http.request(
                "GET",
                "%s/redirect" % self.base_url,
                fields={"target": "/"},
                retries=False,
            )
            assert r.status == 303

            r = http.request(
                "GET",
                "%s/redirect" % self.base_url,
                fields={"target": "/"},
                retries=Retry(redirect=False),
            )
            assert r.status == 303

            with pytest.raises(NewConnectionError):
                http.request(
                    "GET",
                    "http://thishostdoesnotexist.invalid/",
                    timeout=0.001,
                    retries=False,
                )
Пример #15
0
    def test_preserve_chunked_on_retry(self):
        self.chunked_requests = 0

        def socket_handler(listener):
            for _ in range(2):
                sock = listener.accept()[0]
                request = consume_socket(sock)
                if b"transfer-encoding: chunked" in request.split(b"\r\n"):
                    self.chunked_requests += 1

                sock.send(
                    b"HTTP/1.1 429 Too Many Requests\r\n"
                    b"Content-Type: text/plain\r\n"
                    b"Retry-After: 1\r\n"
                    b"\r\n"
                )
                sock.close()

        self._start_server(socket_handler)
        with HTTPConnectionPool(self.host, self.port) as pool:
            retries = Retry(total=1)
            pool.urlopen(
                "GET",
                "/",
                body=iter([b"chunk1", b"chunk2"]),
                preload_content=False,
                retries=retries,
            )
        assert self.chunked_requests == 2
Пример #16
0
 def test_connection_error_retries(self):
     """ ECONNREFUSED error should raise a connection error, with retries """
     port = find_unused_port()
     with HTTPConnectionPool(self.host, port) as pool:
         with pytest.raises(MaxRetryError) as e:
             pool.request("GET", "/", retries=Retry(connect=3))
         assert type(e.value.reason) == NewConnectionError
Пример #17
0
    def test_method_whitelist_with_status_forcelist(self):
        # Falsey method_whitelist means to retry on any method.
        retry = Retry(status_forcelist=[500], method_whitelist=None)
        assert retry.is_retry("GET", status_code=500)
        assert retry.is_retry("POST", status_code=500)

        # Criteria of method_whitelist and status_forcelist are ANDed.
        retry = Retry(status_forcelist=[500], method_whitelist=["POST"])
        assert not retry.is_retry("GET", status_code=500)
        assert retry.is_retry("POST", status_code=500)
Пример #18
0
 def test_backoff_reset_after_redirect(self):
     retry = Retry(total=100, redirect=5, backoff_factor=0.2)
     retry = retry.increment(method="GET")
     retry = retry.increment(method="GET")
     assert retry.get_backoff_time() == 0.4
     redirect_response = HTTPResponse(status=302,
                                      headers={"location": "test"})
     retry = retry.increment(method="GET", response=redirect_response)
     assert retry.get_backoff_time() == 0
     retry = retry.increment(method="GET")
     retry = retry.increment(method="GET")
     assert retry.get_backoff_time() == 0.4
Пример #19
0
    def test_default_method_whitelist_retried(self):
        """Hip should retry methods in the default method whitelist"""
        retry = Retry(total=1, status_forcelist=[418])

        with PoolManager() as http:
            resp = http.request(
                "OPTIONS",
                "%s/successful_retry" % self.base_url,
                headers={"test-name": "test_default_whitelist"},
                retries=retry,
            )
            assert resp.status == 200
Пример #20
0
    def test_retries_wrong_whitelist(self):
        """HTTP response w/ status code not in whitelist shouldn't be retried"""
        retry = Retry(total=1, status_forcelist=[202])

        with PoolManager() as http:
            resp = http.request(
                "GET",
                "%s/successful_retry" % self.base_url,
                headers={"test-name": "test_wrong_whitelist"},
                retries=retry,
            )
            assert resp.status == 418
Пример #21
0
    def test_raise_on_redirect(self):
        with PoolManager() as http:
            r = http.request(
                "GET",
                "%s/redirect" % self.base_url,
                fields={
                    "target": "%s/redirect?target=%s/" % (self.base_url, self.base_url)
                },
                retries=Retry(total=None, redirect=1, raise_on_redirect=False),
            )

            assert r.status == 303
Пример #22
0
    def test_read_retries(self):
        """ Should retry for status codes in the whitelist """
        retry = Retry(read=1, status_forcelist=[418])

        with PoolManager() as http:
            resp = http.request(
                "GET",
                "%s/successful_retry" % self.base_url,
                headers={"test-name": "test_read_retries"},
                retries=retry,
            )
            assert resp.status == 200
Пример #23
0
    def test_read_total_retries(self):
        """ HTTP response w/ status code in the whitelist should be retried """
        headers = {"test-name": "test_read_total_retries"}
        retry = Retry(total=1, status_forcelist=[418])

        with PoolManager() as http:
            resp = http.request(
                "GET",
                "%s/successful_retry" % self.base_url,
                headers=headers,
                retries=retry,
            )
            assert resp.status == 200
Пример #24
0
    def test_retries_wrong_method_list(self):
        """Method not in our whitelist should not be retried, even if code matches"""
        headers = {"test-name": "test_wrong_method_whitelist"}
        retry = Retry(total=1, status_forcelist=[418], method_whitelist=["POST"])

        with PoolManager() as http:
            resp = http.request(
                "GET",
                "%s/successful_retry" % self.base_url,
                headers=headers,
                retries=retry,
            )
            assert resp.status == 418
Пример #25
0
    def test_redirect_cross_host_no_remove_headers(self):
        with PoolManager() as http:
            r = http.request(
                "GET",
                "%s/redirect" % self.base_url,
                fields={"target": "%s/headers" % self.base_url_alt},
                headers={"Authorization": "foo"},
                retries=Retry(remove_headers_on_redirect=[]),
            )

            assert r.status == 200

            data = json.loads(r.data.decode("utf-8"))

            assert data["Authorization"] == "foo"
Пример #26
0
    def test_respect_retry_after_header_sleep(self, retry_after_header,
                                              respect_retry_after_header,
                                              sleep_duration):
        retry = Retry(respect_retry_after_header=respect_retry_after_header)

        # Date header syntax can specify an absolute date; compare this to the
        # time in the parametrized inputs above.
        current_time = mock.MagicMock(return_value=time.mktime(
            datetime.datetime(year=2019, month=6, day=3, hour=11).timetuple()))

        with mock.patch("time.sleep") as sleep_mock, mock.patch(
                "time.time", current_time):
            # for the default behavior, it must be in RETRY_AFTER_STATUS_CODES
            response = HTTPResponse(
                status=503, headers={"Retry-After": retry_after_header})

            retry.sleep(response)

            # The expected behavior is that we'll only sleep if respecting
            # this header (since we won't have any backoff sleep attempts)
            if respect_retry_after_header and sleep_duration is not None:
                sleep_mock.assert_called_with(sleep_duration)
            else:
                sleep_mock.assert_not_called()
Пример #27
0
    def test_retry_return_in_response(self):
        headers = {"test-name": "test_retry_return_in_response"}
        retry = Retry(total=2, status_forcelist=[418])

        with PoolManager() as http:
            resp = http.request(
                "GET",
                "%s/successful_retry" % self.base_url,
                headers=headers,
                retries=retry,
            )
            assert resp.status == 200
            assert resp.retries.total == 1
            assert resp.retries.history == (
                RequestHistory("GET", "/successful_retry", None, 418, None),
            )
Пример #28
0
    def test_retry_reuse_safe(self):
        """ It should be possible to reuse a Retry object across requests """
        headers = {"test-name": "test_retry_safe"}
        retry = Retry(total=1, status_forcelist=[418])

        with PoolManager() as http:
            resp = http.request(
                "GET",
                "%s/successful_retry" % self.base_url,
                headers=headers,
                retries=retry,
            )
            assert resp.status == 200
            resp = http.request(
                "GET",
                "%s/successful_retry" % self.base_url,
                headers=headers,
                retries=retry,
            )
            assert resp.status == 200
Пример #29
0
    def test_too_many_redirects(self):
        with PoolManager() as http:
            with pytest.raises(MaxRetryError):
                http.request(
                    "GET",
                    "%s/redirect" % self.base_url,
                    fields={
                        "target": "%s/redirect?target=%s/"
                        % (self.base_url, self.base_url)
                    },
                    retries=1,
                )

            with pytest.raises(MaxRetryError):
                http.request(
                    "GET",
                    "%s/redirect" % self.base_url,
                    fields={
                        "target": "%s/redirect?target=%s/"
                        % (self.base_url, self.base_url)
                    },
                    retries=Retry(total=None, redirect=1),
                )
Пример #30
0
 def test_parse_retry_after(self, value, expected):
     retry = Retry()
     assert retry.parse_retry_after(value) == expected