Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for any type of URL as long as it can be stringified #3450

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
6 changes: 6 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)`
Expand Down
20 changes: 11 additions & 9 deletions httpx/_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@
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"]


class HasDunderStr(typing.Protocol):
def __str__(self) -> str: ...


class URL:
"""
url = httpx.URL("HTTPS://jo%40email.com:a%20secret@müller.de:1234/pa%20th?search=ab#anchorlink")
Expand Down Expand Up @@ -74,7 +78,9 @@ class URL:
themselves.
"""

def __init__(self, url: URL | str = "", **kwargs: typing.Any) -> None:
def __init__(
self, url: URL | str | HasDunderStr = "", **kwargs: typing.Any
) -> None:
if kwargs:
allowed = {
"scheme": str,
Expand Down Expand Up @@ -113,15 +119,11 @@ 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):
self._uri_reference: ParseResult
if 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:
Expand Down
18 changes: 8 additions & 10 deletions tests/models/test_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
Loading