From cbbcdcfdf1eabb8381807cd6a034252d77265190 Mon Sep 17 00:00:00 2001 From: Marc Bresson Date: Thu, 12 Dec 2024 14:41:32 +0100 Subject: [PATCH 1/8] ENH: add support for any type of URL as long as it can be stringified --- httpx/_urls.py | 5 +---- tests/models/test_url.py | 18 ++++++++---------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/httpx/_urls.py b/httpx/_urls.py index 147a8fa333..a5cb72efca 100644 --- a/httpx/_urls.py +++ b/httpx/_urls.py @@ -118,10 +118,7 @@ def __init__(self, url: URL | str = "", **kwargs: typing.Any) -> None: elif isinstance(url, URL): self._uri_reference = url._uri_reference.copy_with(**kwargs) else: - raise TypeError( - "Invalid type for url. Expected str or httpx.URL," - f" got {type(url)}: {url!r}" - ) + self._uri_reference = urlparse(str(url), **kwargs) @property def scheme(self) -> str: diff --git a/tests/models/test_url.py b/tests/models/test_url.py index 03072e8f5c..493be272e8 100644 --- a/tests/models/test_url.py +++ b/tests/models/test_url.py @@ -452,19 +452,17 @@ def test_url_set(): assert all(url in urls for url in url_set) -# Tests for TypeErrors when instantiating `httpx.URL`. - +def test_custom_object_url(): + class ExternalURLClass: + def __str__(self): + return "https://www.example.com/" -def test_url_invalid_type(): - """ - Ensure that invalid types on `httpx.URL()` raise a `TypeError`. - """ + url = ExternalURLClass() + httpx_url = httpx.URL(url) + assert httpx_url == "https://www.example.com/" - class ExternalURLClass: # representing external URL class - pass - with pytest.raises(TypeError): - httpx.URL(ExternalURLClass()) # type: ignore +# Tests for TypeErrors when instantiating `httpx.URL`. def test_url_with_invalid_component(): From 43f0b4cdd93477c697946640c65d0fbd16716bd6 Mon Sep 17 00:00:00 2001 From: Marc Bresson Date: Thu, 12 Dec 2024 14:45:20 +0100 Subject: [PATCH 2/8] MAINT: simplify the condition of URL type checking --- httpx/_urls.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/httpx/_urls.py b/httpx/_urls.py index a5cb72efca..2cd5ce2544 100644 --- a/httpx/_urls.py +++ b/httpx/_urls.py @@ -113,9 +113,7 @@ def __init__(self, url: URL | str = "", **kwargs: typing.Any) -> None: params = kwargs.pop("params") kwargs["query"] = None if not params else str(QueryParams(params)) - if isinstance(url, str): - self._uri_reference = urlparse(url, **kwargs) - elif isinstance(url, URL): + if isinstance(url, URL): self._uri_reference = url._uri_reference.copy_with(**kwargs) else: self._uri_reference = urlparse(str(url), **kwargs) From e1251ffe4d4627e7c9d20afd9a481884164abb0c Mon Sep 17 00:00:00 2001 From: Marc Bresson Date: Thu, 12 Dec 2024 14:55:42 +0100 Subject: [PATCH 3/8] DOC: add example about broad URL support --- docs/api.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/api.md b/docs/api.md index d01cc649ba..38f41082c0 100644 --- a/docs/api.md +++ b/docs/api.md @@ -112,6 +112,12 @@ what gets sent over the wire.* >>> url = URL("https://example.org/") >>> url.host 'example.org' + +from pydantic import HttpUrl +>>> pydantic_url = HttpUrl("https://example.org/") +>>> url = URL(pydantic_url) +>>> url.host +'example.org' ``` * `def __init__(url, **kwargs)` From 985ea2eba57037518d539865781e056241f58b7e Mon Sep 17 00:00:00 2001 From: Marc Bresson Date: Thu, 12 Dec 2024 14:59:09 +0100 Subject: [PATCH 4/8] TYP: add type Any for URL first argument --- httpx/_urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpx/_urls.py b/httpx/_urls.py index 2cd5ce2544..feda726e73 100644 --- a/httpx/_urls.py +++ b/httpx/_urls.py @@ -74,7 +74,7 @@ class URL: themselves. """ - def __init__(self, url: URL | str = "", **kwargs: typing.Any) -> None: + def __init__(self, url: URL | str | typing.Any = "", **kwargs: typing.Any) -> None: if kwargs: allowed = { "scheme": str, From 7157c6ed4c606fa98e729e7082602f9ebac226cd Mon Sep 17 00:00:00 2001 From: Marc Bresson Date: Thu, 12 Dec 2024 15:04:07 +0100 Subject: [PATCH 5/8] TYP: hardcode self._uri_reference s type for mypy --- httpx/_urls.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/httpx/_urls.py b/httpx/_urls.py index feda726e73..b80fe58c20 100644 --- a/httpx/_urls.py +++ b/httpx/_urls.py @@ -6,7 +6,7 @@ import idna from ._types import QueryParamTypes -from ._urlparse import urlparse +from ._urlparse import ParseResult, urlparse from ._utils import primitive_value_to_str __all__ = ["URL", "QueryParams"] @@ -113,6 +113,7 @@ def __init__(self, url: URL | str | typing.Any = "", **kwargs: typing.Any) -> No params = kwargs.pop("params") kwargs["query"] = None if not params else str(QueryParams(params)) + self._uri_reference: ParseResult if isinstance(url, URL): self._uri_reference = url._uri_reference.copy_with(**kwargs) else: From 68c84525454e1fa3c509dbd6150cd5b8ac797188 Mon Sep 17 00:00:00 2001 From: Marc Bresson <50196352+MarcBresson@users.noreply.github.com> Date: Mon, 16 Dec 2024 09:50:28 +0100 Subject: [PATCH 6/8] DOC: fix typo in code example Co-authored-by: Marcelo Trylesinski --- docs/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.md b/docs/api.md index 38f41082c0..f56a34c856 100644 --- a/docs/api.md +++ b/docs/api.md @@ -113,7 +113,7 @@ what gets sent over the wire.* >>> url.host 'example.org' -from pydantic import HttpUrl +>>> from pydantic import HttpUrl >>> pydantic_url = HttpUrl("https://example.org/") >>> url = URL(pydantic_url) >>> url.host From 34a42e8c9a3ce138295329e73f433dbab17635f5 Mon Sep 17 00:00:00 2001 From: Marc Bresson Date: Mon, 16 Dec 2024 09:56:00 +0100 Subject: [PATCH 7/8] TYP: use duck typing for httpx.URL first argument --- httpx/_urls.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/httpx/_urls.py b/httpx/_urls.py index b80fe58c20..6602bf7745 100644 --- a/httpx/_urls.py +++ b/httpx/_urls.py @@ -12,6 +12,10 @@ __all__ = ["URL", "QueryParams"] +class HasDunderStr(typing.Protocol): + def __str__(self): ... + + class URL: """ url = httpx.URL("HTTPS://jo%40email.com:a%20secret@müller.de:1234/pa%20th?search=ab#anchorlink") @@ -74,7 +78,9 @@ class URL: themselves. """ - def __init__(self, url: URL | str | typing.Any = "", **kwargs: typing.Any) -> None: + def __init__( + self, url: URL | str | HasDunderStr = "", **kwargs: typing.Any + ) -> None: if kwargs: allowed = { "scheme": str, From 0519a1d1c63b6c305e8ea6820780a33f5c14831f Mon Sep 17 00:00:00 2001 From: Marc Bresson Date: Mon, 16 Dec 2024 09:58:06 +0100 Subject: [PATCH 8/8] TYP: add return type for Protocol class used for duck typing --- httpx/_urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpx/_urls.py b/httpx/_urls.py index 6602bf7745..64c264cc77 100644 --- a/httpx/_urls.py +++ b/httpx/_urls.py @@ -13,7 +13,7 @@ class HasDunderStr(typing.Protocol): - def __str__(self): ... + def __str__(self) -> str: ... class URL: