Skip to content

Commit 85e247f

Browse files
authored
feat: make type checkers reject unknown client options (#150)
The option TypedDicts behind **httpx2_options are now closed (PEP 728) via typing_extensions, so ty reports a misspelled or refused option such as verfy= or cert= at type-check time; pyright and mypy already did.
1 parent 287f426 commit 85e247f

3 files changed

Lines changed: 21 additions & 3 deletions

File tree

‎pyproject.toml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ classifiers = [
3434
version = "0"
3535
dependencies = [
3636
"httpx2>=2.0.0,<3.0",
37+
# 4.14.0 is the first whose closed TypedDicts keep their keys on Python 3.14, and the
38+
# cp314 pydantic floor (2.12) needs 4.14.1.
39+
"typing-extensions>=4.14.1",
3740
]
3841

3942
[project.optional-dependencies]

‎src/httpware/client.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from http import HTTPStatus
88

99
import httpx2
10+
import typing_extensions
1011

1112
from httpware._internal import import_checker
1213
from httpware._internal.body_cap import _read_capped, _read_capped_async, _validate_max_response_body_bytes
@@ -75,7 +76,7 @@ def _reject_base_url_query(base_url: httpx2.URL | str) -> None:
7576
raise ValueError(_BASE_URL_QUERY_MESSAGE)
7677

7778

78-
class _ClientOptionsBase(typing.TypedDict, total=False):
79+
class _ClientOptionsBase(typing_extensions.TypedDict, total=False):
7980
base_url: str
8081
headers: dict[str, str] | None
8182
params: dict[str, str] | None
@@ -93,14 +94,14 @@ class _ClientOptionsBase(typing.TypedDict, total=False):
9394
default_encoding: str | Callable[[bytes], str | None]
9495

9596

96-
class _AsyncClientOptions(_ClientOptionsBase, total=False):
97+
class _AsyncClientOptions(_ClientOptionsBase, total=False, closed=True):
9798
"""Keyword arguments `AsyncClient` forwards to the `httpx2.AsyncClient` it owns."""
9899

99100
transport: httpx2.AsyncBaseTransport | None
100101
mounts: Mapping[str, httpx2.AsyncBaseTransport | None] | None
101102

102103

103-
class _ClientOptions(_ClientOptionsBase, total=False):
104+
class _ClientOptions(_ClientOptionsBase, total=False, closed=True):
104105
"""Keyword arguments `Client` forwards to the `httpx2.Client` it owns."""
105106

106107
transport: httpx2.BaseTransport | None

‎tests/test_client_options.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,20 @@ def test_unsupported_option_is_typeerror(client_cls: type, key: str) -> None:
110110
client_cls(**{key: object()})
111111

112112

113+
def test_type_checkers_reject_unsupported_async_options() -> None:
114+
with pytest.raises(TypeError, match="verfy"):
115+
AsyncClient(verfy=True) # ty: ignore[unknown-argument]
116+
with pytest.raises(TypeError, match="cert"):
117+
AsyncClient(cert="client.pem") # ty: ignore[unknown-argument]
118+
119+
120+
def test_type_checkers_reject_unsupported_sync_options() -> None:
121+
with pytest.raises(TypeError, match="verfy"):
122+
Client(verfy=True) # ty: ignore[unknown-argument]
123+
with pytest.raises(TypeError, match="cert"):
124+
Client(cert="client.pem") # ty: ignore[unknown-argument]
125+
126+
113127
@pytest.mark.parametrize("client_cls", [AsyncClient, Client])
114128
def test_follow_redirects_with_body_cap_is_valueerror(client_cls: type) -> None:
115129
with pytest.raises(ValueError, match="follow_redirects"):

0 commit comments

Comments
 (0)