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
MarcBresson marked this conversation as resolved.
Show resolved Hide resolved
>>> pydantic_url = HttpUrl("https://example.org/")
>>> url = URL(pydantic_url)
>>> url.host
'example.org'
```

* `def __init__(url, **kwargs)`
Expand Down
14 changes: 5 additions & 9 deletions httpx/_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will allow any type, which makes the other 2 types in this union irrelevant.

Suggested change
def __init__(self, url: URL | str | typing.Any = "", **kwargs: typing.Any) -> None:
def __init__(self, url: URL | str = "", **kwargs: typing.Any) -> None:

Maybe what you want is something like this?

class HasDunderStr(Protocol):
    def __str__(self): ...

But... Not sure this is useful.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I quite like keeping the first two types. It makes it explicit that there is a great compatibility for them.

I like your idea to use protocol. I'm gonna implement it right away.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note the "But... Not sure this is useful." 👀 👍

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes it explicit what can go in this function. An object that doesn't respect this protocol will effectively raise an error.

if kwargs:
allowed = {
"scheme": str,
Expand Down Expand Up @@ -113,15 +113,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