diff --git a/src/hip/__init__.py b/src/hip/__init__.py index b56fd23..9e4d7bf 100644 --- a/src/hip/__init__.py +++ b/src/hip/__init__.py @@ -1,15 +1,13 @@ -from ._client import * # Client -from ._connection import * # Connection, Transport, ResponseContext -from ._content import * # Binary, Content, File, Files, Form, HTML, JSON, MultiPart, Text -from ._headers import * # Headers -from ._network import * # NetworkBackend, NetworkStream, timeout -from ._parsers import * # HTTPParser, ProtocolError -from ._pool import * # ConnectionPool -from ._request import * # Method, Request -from ._response import * # StatusCode, Response -from ._server import * # Server -from ._streams import * # Stream -from ._urls import * # QueryParams, URL +from . import backends, parsers, transports +from ._client import Client +from ._content import HTML, JSON, Binary, Content, File, Files, Form, MultiPart, Text +from ._headers import Headers +from ._request import Method, Request +from ._response import Response, StatusCode +from ._server import Server +from ._streams import Stream +from ._timeouts import sleep, timeout +from ._urls import URL, QueryParams __all__ = [ "HTML", @@ -17,19 +15,13 @@ "URL", "Binary", "Client", - "Connection", - "ConnectionPool", "Content", "File", "Files", "Form", - "HTTPParser", "Headers", "Method", "MultiPart", - "NetworkBackend", - "NetworkStream", - "ProtocolError", "QueryParams", "Request", "Response", @@ -37,7 +29,9 @@ "StatusCode", "Stream", "Text", - "Transport", - "open_connection", + "backends", + "parsers", + "sleep", "timeout", + "transports", ] diff --git a/src/hip/_connection.py b/src/hip/_connection.py index 21c7d2f..8d1673f 100644 --- a/src/hip/_connection.py +++ b/src/hip/_connection.py @@ -17,6 +17,7 @@ __all__ = [ "Connection", + "ResponseStream", "Transport", "open_connection", ] diff --git a/src/hip/_network.py b/src/hip/_network.py index 1631f90..bd4dff5 100644 --- a/src/hip/_network.py +++ b/src/hip/_network.py @@ -1,11 +1,8 @@ import concurrent.futures -import contextlib -import contextvars import select import socket import ssl import threading -import time import types import typing @@ -16,42 +13,9 @@ from typing_extensions import Self from ._streams import Stream +from ._timeouts import get_current_timeout -__all__ = ["NetworkBackend", "NetworkStream", "timeout"] - -_timeout_stack: contextvars.ContextVar[tuple[float, ...]] = contextvars.ContextVar( - "timeout_context", default=() -) - - -@contextlib.contextmanager -def timeout(duration: float) -> typing.Iterator[None]: - """ - A context managed timeout API. - - with timeout(1.0): - ... - """ - now = time.monotonic() - until = now + duration - stack = (until, *_timeout_stack.get()) - token = _timeout_stack.set(stack) - try: - yield - finally: - _timeout_stack.reset(token) - - -def get_current_timeout() -> float | None: - stack = _timeout_stack.get() - if not stack: - return None - soonest = min(stack) - now = time.monotonic() - remaining = soonest - now - if remaining <= 0.0: - raise TimeoutError() - return remaining +__all__ = ["NetworkBackend", "NetworkStream"] class NetworkStream(Stream): @@ -249,4 +213,3 @@ def __repr__(self): Semaphore = threading.Semaphore Lock = threading.Lock -sleep = time.sleep diff --git a/src/hip/_server.py b/src/hip/_server.py index 8d98f6d..968260c 100644 --- a/src/hip/_server.py +++ b/src/hip/_server.py @@ -4,10 +4,11 @@ from ._body import HTTPStream from ._content import Text from ._headers import Headers -from ._network import NetworkBackend, sleep +from ._network import NetworkBackend from ._parsers import HTTPParser from ._request import Request from ._response import Response +from ._timeouts import sleep __all__ = ["Server"] diff --git a/src/hip/_timeouts.py b/src/hip/_timeouts.py new file mode 100644 index 0000000..7a6662d --- /dev/null +++ b/src/hip/_timeouts.py @@ -0,0 +1,52 @@ +import contextlib +import contextvars +import time +import typing + +__all__ = ["sleep", "timeout"] + + +_timeout_stack: contextvars.ContextVar[tuple[float, ...]] = contextvars.ContextVar( + "timeout_context", default=() +) + + +@contextlib.contextmanager +def timeout(duration: float) -> typing.Iterator[None]: + """ + A context managed timeout API. + + with timeout(1.0): + ... + """ + now = time.monotonic() + until = now + duration + stack = (until, *_timeout_stack.get()) + token = _timeout_stack.set(stack) + try: + yield + finally: + _timeout_stack.reset(token) + + +def get_current_timeout() -> float | None: + stack = _timeout_stack.get() + if not stack: + return None + soonest = min(stack) + now = time.monotonic() + remaining = soonest - now + if remaining <= 0.0: + raise TimeoutError() + return remaining + + +def sleep(dur: float): + """ + A `sleep()` implementation that is aware of the current timeout context. + """ + remaining = get_current_timeout() + if remaining is not None and remaining <= dur: # pragma: nocover + time.sleep(remaining) + raise TimeoutError() + time.sleep(dur) diff --git a/src/hip/backends.py b/src/hip/backends.py new file mode 100644 index 0000000..02ecb93 --- /dev/null +++ b/src/hip/backends.py @@ -0,0 +1,6 @@ +from ._network import NetworkBackend, NetworkStream + +__all__ = [ + "NetworkBackend", + "NetworkStream", +] diff --git a/src/hip/parsers.py b/src/hip/parsers.py new file mode 100644 index 0000000..9b54ee0 --- /dev/null +++ b/src/hip/parsers.py @@ -0,0 +1,6 @@ +from ._parsers import HTTPParser, ProtocolError + +__all__ = [ + "HTTPParser", + "ProtocolError", +] diff --git a/src/hip/transports.py b/src/hip/transports.py new file mode 100644 index 0000000..e1e24a4 --- /dev/null +++ b/src/hip/transports.py @@ -0,0 +1,10 @@ +from ._connection import Connection, ResponseStream, Transport, open_connection +from ._pool import ConnectionPool + +__all__ = [ + "Connection", + "ConnectionPool", + "ResponseStream", + "Transport", + "open_connection", +] diff --git a/tests/test_network.py b/tests/test_network.py index 6d215d6..8359ca5 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -10,18 +10,18 @@ def echo(stream): @pytest.fixture def server(): - net = hip.NetworkBackend() + net = hip.backends.NetworkBackend() with net.serve("127.0.0.1", 8080, echo) as server: yield server def test_network_backend(): - net = hip.NetworkBackend() + net = hip.backends.NetworkBackend() assert repr(net) in ["", ""] def test_network_backend_connect(server): - net = hip.NetworkBackend() + net = hip.backends.NetworkBackend() stream = net.connect(server.host, server.port) try: assert repr(stream) == f"" @@ -61,7 +61,7 @@ def shutdown(self, how): def test_network_stream_close_ignores_shutdown_errors(): sock = FailingShutdownSocket() - stream = hip.NetworkStream(sock, ("example.com", 443)) + stream = hip.backends.NetworkStream(sock, ("example.com", 443)) stream.close() @@ -72,7 +72,7 @@ def test_network_backend_connect_tls(monkeypatch): raw_socket = FakeSocket() tls_socket = FakeSocket() ssl_context = FakeSSLContext(tls_socket) - backend = hip.NetworkBackend(ssl_ctx=ssl_context) + backend = hip.backends.NetworkBackend(ssl_ctx=ssl_context) create_connection_args = [] @@ -92,7 +92,7 @@ def create_connection(address, timeout): def test_network_listener_context_managed(): - backend = hip.NetworkBackend() + backend = hip.backends.NetworkBackend() with backend.listen("127.0.0.1", 0) as listener: assert listener.host == "127.0.0.1" @@ -101,7 +101,7 @@ def test_network_listener_context_managed(): def test_network_backend_context_managed(server): - net = hip.NetworkBackend() + net = hip.backends.NetworkBackend() with net.connect(server.host, server.port) as stream: stream.write(b"Hello, world.") content = stream.read() @@ -110,7 +110,7 @@ def test_network_backend_context_managed(server): def test_network_backend_timeout(server): - net = hip.NetworkBackend() + net = hip.backends.NetworkBackend() with ( hip.timeout(0.0), pytest.raises(TimeoutError), diff --git a/tests/test_parsers.py b/tests/test_parsers.py index be9d6b8..b6a608f 100644 --- a/tests/test_parsers.py +++ b/tests/test_parsers.py @@ -57,7 +57,7 @@ def test_parser(): b"hello, world" ) - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"POST", b"/", b"HTTP/1.1") p.send_headers( [ @@ -108,7 +108,7 @@ def test_parser(): def test_parser_server(): stream = DuplexStream(b"GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n") - p = hip.HTTPParser(stream, mode="SERVER") + p = hip.parsers.HTTPParser(stream, mode="SERVER") method, target, protocol = p.recv_method_line() headers = p.recv_headers() body = p.recv_body() @@ -146,7 +146,7 @@ def test_parser_server(): def test_empty_request_line_raises_eof_error(): - parser = hip.HTTPParser(DuplexStream(b""), mode="SERVER") + parser = hip.parsers.HTTPParser(DuplexStream(b""), mode="SERVER") with pytest.raises(EOFError): parser.recv_method_line() @@ -154,21 +154,22 @@ def test_empty_request_line_raises_eof_error(): def test_incomplete_request_line_raises_protocol_error_on_eof(): stream = DuplexStream(b"GE") - parser = hip.HTTPParser(stream, mode="SERVER") + parser = hip.parsers.HTTPParser(stream, mode="SERVER") with pytest.raises( - hip.ProtocolError, match=r"Stream closed early reading request method line\." + hip.parsers.ProtocolError, + match=r"Stream closed early reading request method line\.", ): parser.recv_method_line() def test_server_headers_eof_remains_protocol_error(): stream = DuplexStream(b"GET / HTTP/1.1\r\nHost: example.com\r\n") - parser = hip.HTTPParser(stream, mode="SERVER") + parser = hip.parsers.HTTPParser(stream, mode="SERVER") parser.recv_method_line() with pytest.raises( - hip.ProtocolError, match="Stream closed early reading response headers" + hip.parsers.ProtocolError, match="Stream closed early reading response headers" ): parser.recv_headers() @@ -182,7 +183,7 @@ def test_parser_trickle(): b"hello, world" ) - p = hip.HTTPParser(TrickleIO(stream), mode="CLIENT") + p = hip.parsers.HTTPParser(TrickleIO(stream), mode="CLIENT") p.send_method_line(b"POST", b"/", b"HTTP/1.1") p.send_headers( [ @@ -237,7 +238,7 @@ def test_parser_transfer_encoding_chunked(): b"0\r\n\r\n" ) - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"POST", b"/", b"HTTP/1.1") p.send_headers( [ @@ -296,7 +297,7 @@ def test_parser_transfer_encoding_chunked_trickle(): b"0\r\n\r\n" ) - p = hip.HTTPParser(TrickleIO(stream), mode="CLIENT") + p = hip.parsers.HTTPParser(TrickleIO(stream), mode="CLIENT") p.send_method_line(b"POST", b"/", b"HTTP/1.1") p.send_headers( [ @@ -353,7 +354,7 @@ def test_parser_repr(): b'{"msg": "hello, world"}' ) - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") assert repr(p) == "" p.send_method_line(b"GET", b"/", b"HTTP/1.1") @@ -384,31 +385,31 @@ def test_parser_repr(): def test_parser_invalid_transitions(): stream = DuplexStream() - with pytest.raises(hip.ProtocolError): - p = hip.HTTPParser(stream, mode="CLIENT") + with pytest.raises(hip.parsers.ProtocolError): + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"GET", b"/", b"HTTP/1.1") p.send_method_line(b"GET", b"/", b"HTTP/1.1") - with pytest.raises(hip.ProtocolError): - p = hip.HTTPParser(stream, mode="CLIENT") + with pytest.raises(hip.parsers.ProtocolError): + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_headers([]) - with pytest.raises(hip.ProtocolError): - p = hip.HTTPParser(stream, mode="CLIENT") + with pytest.raises(hip.parsers.ProtocolError): + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_body(b"") - with pytest.raises(hip.ProtocolError): + with pytest.raises(hip.parsers.ProtocolError): b = hip.Binary(b"HTTP/1.1 200 OK\r\n") with b.open() as stream: - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.recv_status_line() - with pytest.raises(hip.ProtocolError): - p = hip.HTTPParser(stream, mode="CLIENT") + with pytest.raises(hip.parsers.ProtocolError): + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.recv_headers() - with pytest.raises(hip.ProtocolError): - p = hip.HTTPParser(stream, mode="CLIENT") + with pytest.raises(hip.parsers.ProtocolError): + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.recv_body() @@ -416,37 +417,37 @@ def test_parser_invalid_status_line(): # ... stream = DuplexStream(b"...") - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"GET", b"/", b"HTTP/1.1") p.send_headers([(b"Host", b"example.com")]) p.send_body(b"") msg = "Stream closed early reading response status line" - with pytest.raises(hip.ProtocolError, match=msg): + with pytest.raises(hip.parsers.ProtocolError, match=msg): p.recv_status_line() # ... stream = DuplexStream(b"HTTP/1.1" + b"x" * 5000) - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"GET", b"/", b"HTTP/1.1") p.send_headers([(b"Host", b"example.com")]) p.send_body(b"") msg = "Exceeded maximum size reading response status line" - with pytest.raises(hip.ProtocolError, match=msg): + with pytest.raises(hip.parsers.ProtocolError, match=msg): p.recv_status_line() # ... stream = DuplexStream(b"HTTP/1.1" + b"x" * 5000 + b"\r\n") - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"GET", b"/", b"HTTP/1.1") p.send_headers([(b"Host", b"example.com")]) p.send_body(b"") msg = "Exceeded maximum size reading response status line" - with pytest.raises(hip.ProtocolError, match=msg): + with pytest.raises(hip.parsers.ProtocolError, match=msg): p.recv_status_line() @@ -454,9 +455,9 @@ def test_parser_sent_unsupported_protocol(): # Currently only HTTP/1.1 is supported. stream = DuplexStream() - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") msg = "Sent unsupported protocol version" - with pytest.raises(hip.ProtocolError, match=msg): + with pytest.raises(hip.parsers.ProtocolError, match=msg): p.send_method_line(b"GET", b"/", b"HTTP/1.0") @@ -464,10 +465,10 @@ def test_parser_recv_unsupported_protocol(): # Currently only HTTP/1.1 is supported. stream = DuplexStream(b"HTTP/1.0 200 OK\r\n") - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"GET", b"/", b"HTTP/1.1") msg = "Received unsupported protocol version" - with pytest.raises(hip.ProtocolError, match=msg): + with pytest.raises(hip.parsers.ProtocolError, match=msg): p.recv_status_line() @@ -481,7 +482,7 @@ def test_parser_large_body(): b"\r\n" + body ) - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"GET", b"/", b"HTTP/1.1") p.send_headers([(b"Host", b"example.com")]) p.send_body(b"") @@ -505,7 +506,7 @@ def test_parser_stream_large_body(): b"1b2c\r\n" + body + b"\r\n0\r\n\r\n" ) - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"GET", b"/", b"HTTP/1.1") p.send_headers([(b"Host", b"example.com")]) p.send_body(b"") @@ -528,7 +529,7 @@ def test_parser_not_enough_data_received(): b"truncated" ) - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"GET", b"/", b"HTTP/1.1") p.send_headers([(b"Host", b"example.com")]) p.send_body(b"") @@ -538,14 +539,14 @@ def test_parser_not_enough_data_received(): p.recv_headers() p.recv_body() msg = "Not enough data received for declared Content-Length" - with pytest.raises(hip.ProtocolError, match=msg): + with pytest.raises(hip.parsers.ProtocolError, match=msg): p.recv_body() def test_parser_not_enough_data_sent(): stream = DuplexStream() - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"POST", b"/", b"HTTP/1.1") p.send_headers( [ @@ -556,14 +557,14 @@ def test_parser_not_enough_data_sent(): ) p.send_body(b'{"msg": "too smol"}') msg = "Not enough data sent for declared Content-Length" - with pytest.raises(hip.ProtocolError, match=msg): + with pytest.raises(hip.parsers.ProtocolError, match=msg): p.send_body(b"") def test_parser_too_much_data_sent(): stream = DuplexStream() - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"POST", b"/", b"HTTP/1.1") p.send_headers( [ @@ -573,17 +574,17 @@ def test_parser_too_much_data_sent(): ] ) msg = "Too much data sent for declared Content-Length" - with pytest.raises(hip.ProtocolError, match=msg): + with pytest.raises(hip.parsers.ProtocolError, match=msg): p.send_body(b'{"msg": "too chonky"}') def test_parser_missing_host_header(): stream = DuplexStream() - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"GET", b"/", b"HTTP/1.1") msg = "Request missing 'Host' header" - with pytest.raises(hip.ProtocolError, match=msg): + with pytest.raises(hip.parsers.ProtocolError, match=msg): p.send_headers([]) @@ -596,7 +597,7 @@ def test_client_connection_close(): b"hello, world" ) - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"GET", b"/", b"HTTP/1.1") p.send_headers( [ @@ -638,7 +639,7 @@ def test_server_connection_close(): b"hello, world" ) - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"GET", b"/", b"HTTP/1.1") p.send_headers([(b"Host", b"example.com")]) p.send_body(b"") @@ -673,7 +674,7 @@ def test_invalid_status_code(): b"hello, world" ) - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"GET", b"/", b"HTTP/1.1") p.send_headers( [ @@ -684,7 +685,7 @@ def test_invalid_status_code(): p.send_body(b"") msg = "Received invalid status code" - with pytest.raises(hip.ProtocolError, match=msg): + with pytest.raises(hip.parsers.ProtocolError, match=msg): p.recv_status_line() @@ -701,7 +702,7 @@ def test_1xx_status_code(): b"hello, world" ) - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"GET", b"/", b"HTTP/1.1") p.send_headers([(b"Host", b"example.com")]) p.send_body(b"") @@ -742,7 +743,7 @@ def test_received_invalid_content_length(): b"hello, world" ) - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"GET", b"/", b"HTTP/1.1") p.send_headers( [ @@ -754,17 +755,17 @@ def test_received_invalid_content_length(): p.recv_status_line() msg = "Received invalid Content-Length" - with pytest.raises(hip.ProtocolError, match=msg): + with pytest.raises(hip.parsers.ProtocolError, match=msg): p.recv_headers() def test_sent_invalid_content_length(): stream = DuplexStream() - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"GET", b"/", b"HTTP/1.1") msg = "Sent invalid Content-Length" - with pytest.raises(hip.ProtocolError, match=msg): + with pytest.raises(hip.parsers.ProtocolError, match=msg): # Limited to 20 digits. # 100 million terabytes should be enough for anyone. p.send_headers( @@ -784,7 +785,7 @@ def test_received_invalid_characters_in_chunk_size(): b"0xFF\r\n..." ) - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"GET", b"/", b"HTTP/1.1") p.send_headers( [ @@ -797,7 +798,7 @@ def test_received_invalid_characters_in_chunk_size(): p.recv_status_line() p.recv_headers() msg = "Received invalid chunk size" - with pytest.raises(hip.ProtocolError, match=msg): + with pytest.raises(hip.parsers.ProtocolError, match=msg): p.recv_body() @@ -810,7 +811,7 @@ def test_received_oversized_chunk(): b"FFFFFFFFFF\r\n..." ) - p = hip.HTTPParser(stream, mode="CLIENT") + p = hip.parsers.HTTPParser(stream, mode="CLIENT") p.send_method_line(b"GET", b"/", b"HTTP/1.1") p.send_headers( [ @@ -823,53 +824,53 @@ def test_received_oversized_chunk(): p.recv_status_line() p.recv_headers() msg = "Received invalid chunk size" - with pytest.raises(hip.ProtocolError, match=msg): + with pytest.raises(hip.parsers.ProtocolError, match=msg): p.recv_body() def test_send_response_before_request(): stream = DuplexStream() - p = hip.HTTPParser(stream, mode="SERVER") + p = hip.parsers.HTTPParser(stream, mode="SERVER") msg = "Called 'send_status_line' in invalid state State.WAIT" - with pytest.raises(hip.ProtocolError, match=msg): + with pytest.raises(hip.parsers.ProtocolError, match=msg): p.send_status_line(b"HTTP/1.1", 200, b"OK") def test_server_receives_unsupported_protocol(): stream = DuplexStream(b"GET / HTTP/1.0\r\n") - p = hip.HTTPParser(stream, mode="SERVER") + p = hip.parsers.HTTPParser(stream, mode="SERVER") msg = "Received unsupported protocol version" - with pytest.raises(hip.ProtocolError, match=msg): + with pytest.raises(hip.parsers.ProtocolError, match=msg): p.recv_method_line() def test_server_sends_unsupported_protocol(): stream = DuplexStream(b"GET / HTTP/1.1\r\n") - p = hip.HTTPParser(stream, mode="SERVER") + p = hip.parsers.HTTPParser(stream, mode="SERVER") p.recv_method_line() msg = "Sent unsupported protocol version" - with pytest.raises(hip.ProtocolError, match=msg): + with pytest.raises(hip.parsers.ProtocolError, match=msg): p.send_status_line(b"HTTP/2.0", 200, b"OK") def test_server_receives_method_line_twice(): stream = DuplexStream(b"GET / HTTP/1.1\r\n") - p = hip.HTTPParser(stream, mode="SERVER") + p = hip.parsers.HTTPParser(stream, mode="SERVER") p.recv_method_line() msg = "Called 'recv_method_line' in invalid state State.RECV_HEADERS" - with pytest.raises(hip.ProtocolError, match=msg): + with pytest.raises(hip.parsers.ProtocolError, match=msg): p.recv_method_line() def test_request_missing_host_header(): stream = DuplexStream(b"GET / HTTP/1.1\r\n\r\n") - p = hip.HTTPParser(stream, mode="SERVER") + p = hip.parsers.HTTPParser(stream, mode="SERVER") p.recv_method_line() msg = "Request missing 'Host' header" - with pytest.raises(hip.ProtocolError, match=msg): + with pytest.raises(hip.parsers.ProtocolError, match=msg): p.recv_headers() diff --git a/tests/test_pool.py b/tests/test_pool.py index a40ad8a..b48f71e 100644 --- a/tests/test_pool.py +++ b/tests/test_pool.py @@ -15,7 +15,7 @@ def server(): def test_connection_pool_request(server): - with hip.ConnectionPool() as pool: + with hip.transports.ConnectionPool() as pool: assert repr(pool) == "" assert len(pool.connections) == 0 @@ -27,18 +27,18 @@ def test_connection_pool_request(server): def test_transport_request_not_implemented(): - transport = hip.Transport() + transport = hip.transports.Transport() with pytest.raises(NotImplementedError): transport.request("GET", "http://example.com") def test_transport_close(): - hip.Transport().close() + hip.transports.Transport().close() def test_connection_pool_connection_close(server): - with hip.ConnectionPool() as pool: + with hip.transports.ConnectionPool() as pool: assert repr(pool) == "" assert len(pool.connections) == 0 @@ -51,7 +51,7 @@ def test_connection_pool_connection_close(server): def test_connection_pool_stream(server): - with hip.ConnectionPool() as pool: + with hip.transports.ConnectionPool() as pool: assert repr(pool) == "" assert len(pool.connections) == 0 @@ -66,7 +66,7 @@ def test_connection_pool_stream(server): def test_connection_pool_cannot_request_after_closed(): - with hip.ConnectionPool() as pool: + with hip.transports.ConnectionPool() as pool: pass with pytest.raises(RuntimeError): @@ -74,13 +74,13 @@ def test_connection_pool_cannot_request_after_closed(): def test_connection_pool_should_have_managed_lifespan(server): - pool = hip.ConnectionPool() + pool = hip.transports.ConnectionPool() with pytest.warns(UserWarning): del pool def test_connection_request(server): - with hip.open_connection(server.url) as conn: + with hip.transports.open_connection(server.url) as conn: assert repr(conn) == f"" r = conn.request("GET", "/") @@ -90,7 +90,7 @@ def test_connection_request(server): def test_connection_stream(server): - with hip.open_connection(server.url) as conn: + with hip.transports.open_connection(server.url) as conn: assert repr(conn) == f"" with conn.stream("GET", "/") as r: assert r.status_code == 200 @@ -100,7 +100,10 @@ def test_connection_stream(server): def test_connection_stream_read_in_chunks(server): - with hip.open_connection(server.url) as conn, conn.stream("GET", "/") as response: + with ( + hip.transports.open_connection(server.url) as conn, + conn.stream("GET", "/") as response, + ): assert response.read(5) == b"Hello" assert response.read(5) == b", wor" assert response.read() == b"ld." @@ -115,7 +118,7 @@ def connect_tls(self, host, port, hostname): def test_open_https_connection(): backend = TLSBackend() - with hip.open_connection( + with hip.transports.open_connection( "https://example.com", hostname="example.com", backend=backend, diff --git a/tests/test_timeouts.py b/tests/test_timeouts.py new file mode 100644 index 0000000..565ada1 --- /dev/null +++ b/tests/test_timeouts.py @@ -0,0 +1,17 @@ +import pytest + +import hip + + +def test_timeout_not_expired(): + with hip.timeout(5.0): + hip.sleep(0.0) + + +def test_timeout_expired(): + with hip.timeout(0.0), pytest.raises(TimeoutError): + hip.sleep(0.0) + + +def test_no_timeout_set(): + hip.sleep(0.0)