From 14d1c0d39bbb9c033757560fd800c3a521780143 Mon Sep 17 00:00:00 2001 From: Kim Christie Date: Sat, 31 Oct 2026 14:46:37 +0100 Subject: [PATCH 1/5] Use Connection(origin, stream) --- src/hip/_connection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hip/_connection.py b/src/hip/_connection.py index f8f4f4c..8d7ea51 100644 --- a/src/hip/_connection.py +++ b/src/hip/_connection.py @@ -80,7 +80,7 @@ def stream( class Connection(Transport): - def __init__(self, stream: Stream, origin: URL | str): + def __init__(self, origin: URL | str, stream: Stream): self._stream = stream self._origin = URL(origin) if not isinstance(origin, URL) else origin self._request_lock = Lock() @@ -213,4 +213,4 @@ def open_connection( else: stream = backend.connect(host, port) - return Connection(stream, url) + return Connection(origin=url, stream=stream) From 2469089a5075db0ae275657d2e71e6c4b3e07018 Mon Sep 17 00:00:00 2001 From: Kim Christie Date: Sat, 31 Oct 2026 14:55:47 +0100 Subject: [PATCH 2/5] Allow stream to be optional on connection init --- src/hip/_connection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hip/_connection.py b/src/hip/_connection.py index 8d7ea51..684933f 100644 --- a/src/hip/_connection.py +++ b/src/hip/_connection.py @@ -80,11 +80,11 @@ def stream( class Connection(Transport): - def __init__(self, origin: URL | str, stream: Stream): - self._stream = stream + def __init__(self, origin: URL | str, stream: Stream | None = None): + self._stream = Stream() if (stream is None) else stream self._origin = URL(origin) if not isinstance(origin, URL) else origin self._request_lock = Lock() - self._parser = HTTPParser(stream, mode="CLIENT") + self._parser = HTTPParser(self._stream, mode="CLIENT") # API for connection pool management... def origin(self) -> URL: From e0ee82333c6b58fdf6765c9ea8bef9081e4530d2 Mon Sep 17 00:00:00 2001 From: Kim Christie Date: Sat, 31 Oct 2026 15:42:57 +0100 Subject: [PATCH 3/5] Use context manager for Connection() open/close --- src/hip/_connection.py | 43 ++++++++++++++++++------------------------ src/hip/_pool.py | 7 +++---- src/hip/transports.py | 3 +-- tests/test_pool.py | 23 +++++++++++----------- 4 files changed, 33 insertions(+), 43 deletions(-) diff --git a/src/hip/_connection.py b/src/hip/_connection.py index 684933f..0a1a515 100644 --- a/src/hip/_connection.py +++ b/src/hip/_connection.py @@ -86,6 +86,23 @@ def __init__(self, origin: URL | str, stream: Stream | None = None): self._request_lock = Lock() self._parser = HTTPParser(self._stream, mode="CLIENT") + # Ensure valid URL + if self._origin.scheme not in ('http', 'https'): + raise ValueError(f"Invalid URL. Expected http:// or https://, got {self._origin.scheme}://") + + def open(self, backend: NetworkBackend | None = None) -> None: + backend = NetworkBackend() if (backend is None) else backend + + scheme = self._origin.scheme + host = self._origin.host + port = self._origin.port or {"http": 80, "https": 443}[scheme] + + if scheme == "https": + self._stream = backend.connect_tls(host, port) + else: + self._stream = backend.connect(host, port) + self._parser = HTTPParser(self._stream, mode="CLIENT") + # API for connection pool management... def origin(self) -> URL: return self._origin @@ -180,6 +197,7 @@ def __repr__(self) -> str: return f"" def __enter__(self) -> "Self": + self.open() return self def __exit__( @@ -189,28 +207,3 @@ def __exit__( traceback: types.TracebackType | None = None, ): self.close() - - -def open_connection( - url: URL | str, - hostname: str = "", - backend: NetworkBackend | None = None, -) -> Connection: - - if isinstance(url, str): - url = URL(url) - - if url.scheme not in ("http", "https"): - raise ValueError("URL scheme must be 'http://' or 'https://'.") - if backend is None: - backend = NetworkBackend() - - host = url.host - port = url.port or {"http": 80, "https": 443}[url.scheme] - - if url.scheme == "https": - stream = backend.connect_tls(host, port, hostname) - else: - stream = backend.connect(host, port) - - return Connection(origin=url, stream=stream) diff --git a/src/hip/_pool.py b/src/hip/_pool.py index 812056c..0527a2c 100644 --- a/src/hip/_pool.py +++ b/src/hip/_pool.py @@ -6,7 +6,7 @@ # Once we drop 3.10 support, we can use 'typing.Self'. from typing_extensions import Self -from ._connection import Connection, ResponseStream, Transport, open_connection +from ._connection import Connection, ResponseStream, Transport from ._request import Request from ._urls import URL from .backends.sync import NetworkBackend, Semaphore @@ -53,9 +53,8 @@ def _get_connection(self, request: Request) -> "Connection": return conn # Or else create a new connection. - conn = open_connection( - origin, hostname=request.headers["Host"], backend=self._network_backend - ) + conn = Connection(origin) + conn.open(backend=self._network_backend) self._connections.append(conn) return conn diff --git a/src/hip/transports.py b/src/hip/transports.py index e1e24a4..4ec7c0d 100644 --- a/src/hip/transports.py +++ b/src/hip/transports.py @@ -1,4 +1,4 @@ -from ._connection import Connection, ResponseStream, Transport, open_connection +from ._connection import Connection, ResponseStream, Transport from ._pool import ConnectionPool __all__ = [ @@ -6,5 +6,4 @@ "ConnectionPool", "ResponseStream", "Transport", - "open_connection", ] diff --git a/tests/test_pool.py b/tests/test_pool.py index b48f71e..eb2846b 100644 --- a/tests/test_pool.py +++ b/tests/test_pool.py @@ -80,7 +80,7 @@ def test_connection_pool_should_have_managed_lifespan(server): def test_connection_request(server): - with hip.transports.open_connection(server.url) as conn: + with hip.transports.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.transports.open_connection(server.url) as conn: + with hip.transports.Connection(server.url) as conn: assert repr(conn) == f"" with conn.stream("GET", "/") as r: assert r.status_code == 200 @@ -101,7 +101,7 @@ def test_connection_stream(server): def test_connection_stream_read_in_chunks(server): with ( - hip.transports.open_connection(server.url) as conn, + hip.transports.Connection(server.url) as conn, conn.stream("GET", "/") as response, ): assert response.read(5) == b"Hello" @@ -110,19 +110,18 @@ def test_connection_stream_read_in_chunks(server): class TLSBackend: - def connect_tls(self, host, port, hostname): - self.connect_tls_args = (host, port, hostname) + def connect_tls(self, host, port): + self.connect_tls_args = (host, port) return hip.Binary(b"").open() def test_open_https_connection(): backend = TLSBackend() - with hip.transports.open_connection( - "https://example.com", - hostname="example.com", - backend=backend, - ): - pass + conn = hip.transports.Connection("https://example.com") + conn.open(backend=backend) + try: + assert backend.connect_tls_args == ("example.com", 443) + finally: + conn.close() - assert backend.connect_tls_args == ("example.com", 443, "example.com") From b2647328f137d1ab83c4d49eb74e1dadf72917e0 Mon Sep 17 00:00:00 2001 From: Kim Christie Date: Sat, 31 Oct 2026 22:26:33 +0100 Subject: [PATCH 4/5] Trailing whitespace --- tests/test_pool.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_pool.py b/tests/test_pool.py index eb2846b..b3978a1 100644 --- a/tests/test_pool.py +++ b/tests/test_pool.py @@ -124,4 +124,3 @@ def test_open_https_connection(): assert backend.connect_tls_args == ("example.com", 443) finally: conn.close() - From caa69d382d70114f6a006ae90048087a1e4a6d56 Mon Sep 17 00:00:00 2001 From: Kim Christie Date: Sat, 31 Oct 2026 22:48:10 +0100 Subject: [PATCH 5/5] Linting --- src/hip/_connection.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/hip/_connection.py b/src/hip/_connection.py index 0a1a515..5e36a47 100644 --- a/src/hip/_connection.py +++ b/src/hip/_connection.py @@ -19,7 +19,6 @@ "Connection", "ResponseStream", "Transport", - "open_connection", ] @@ -87,8 +86,10 @@ def __init__(self, origin: URL | str, stream: Stream | None = None): self._parser = HTTPParser(self._stream, mode="CLIENT") # Ensure valid URL - if self._origin.scheme not in ('http', 'https'): - raise ValueError(f"Invalid URL. Expected http:// or https://, got {self._origin.scheme}://") + if self._origin.scheme not in ("http", "https"): + raise ValueError( + f"Invalid URL. Expected http:// or https://, got {self._origin.scheme}://" + ) def open(self, backend: NetworkBackend | None = None) -> None: backend = NetworkBackend() if (backend is None) else backend