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

Revert percent encoding in URLs. #4470

Merged
merged 2 commits into from Sep 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/4470-samuelcolvin.md
@@ -0,0 +1 @@
**Revert Change:** Revert percent encoding of URL parts which was originally added in #4224.
10 changes: 0 additions & 10 deletions docs/examples/types_url_building.py

This file was deleted.

20 changes: 5 additions & 15 deletions docs/usage/types.md
Expand Up @@ -616,7 +616,11 @@ For URI/URL validation the following types are available:
- `tld_required: bool = True`
- `host_required: bool = True`
- `allowed_schemes: Optional[Set[str]] = None`
- `quote_plus: bool = False`

!!! warning
In V1.10.0 and v1.10.1 `stricturl` also took an optional `quote_plus` argument and URL components were percent
encoded in some cases. This feature was removed in v1.10.2, see
[#4470](https://github.com/pydantic/pydantic/pull/4470) for explanation and more details.

The above types (which all inherit from `AnyUrl`) will attempt to give descriptive errors when invalid URLs are
provided:
Expand Down Expand Up @@ -679,20 +683,6 @@ If further validation is required, these properties can be used by validators to
Also, Chrome, Firefox, and Safari all currently accept `http://exam_ple.com` as a URL, so we're in good
(or at least big) company.

#### Building URLs

You can build URLs from separate [URL Properties](#url-properties) using the `build` method in
[Pydantic URL types](#urls) or any type that inherits from them.

By default, *pydantic* percent encodes the following URL properties: `user`, `password`, `path`, `query`
as per [RFC 3986](https://www.ietf.org/rfc/rfc3986.txt) without replacing spaces with `+` but this can
be changed using the `stricturl` method:

!!! note
Percent encoding was added in V1.10

{!.tmp_examples/types_url_building.md!}

### Color Type

You can use the `Color` data type for storing colors as per
Expand Down
17 changes: 4 additions & 13 deletions pydantic/networks.py
Expand Up @@ -26,7 +26,6 @@
cast,
no_type_check,
)
from urllib.parse import quote, quote_plus

from . import errors
from .utils import Representation, update_not_none
Expand Down Expand Up @@ -178,7 +177,6 @@ class AnyUrl(str):
user_required: bool = False
host_required: bool = True
hidden_parts: Set[str] = set()
quote_plus: bool = False

__slots__ = ('scheme', 'user', 'password', 'host', 'tld', 'host_type', 'port', 'path', 'query', 'fragment')

Expand Down Expand Up @@ -241,19 +239,18 @@ def build(

url = scheme + '://'
if user:
url += cls.quote(user)
url += user
if password:
url += ':' + cls.quote(password)
url += ':' + password
if user or password:
url += '@'
url += host
if port and ('port' not in cls.hidden_parts or cls.get_default_parts(parts).get('port') != port):
url += ':' + port
if path:
url += '/'.join(map(cls.quote, path.split('/')))
url += path
if query:
queries = query.split('&')
url += '?' + '&'.join(map(lambda s: '='.join(map(cls.quote, s.split('='))), queries))
url += '?' + query
if fragment:
url += '#' + fragment
return url
Expand Down Expand Up @@ -394,10 +391,6 @@ def apply_default_parts(cls, parts: 'Parts') -> 'Parts':
parts[key] = value # type: ignore[literal-required]
return parts

@classmethod
def quote(cls, string: str, safe: str = '') -> str:
return quote_plus(string, safe) if cls.quote_plus else quote(string, safe)

def __repr__(self) -> str:
extra = ', '.join(f'{n}={getattr(self, n)!r}' for n in self.__slots__ if getattr(self, n) is not None)
return f'{self.__class__.__name__}({super().__repr__()}, {extra})'
Expand Down Expand Up @@ -565,7 +558,6 @@ def stricturl(
tld_required: bool = True,
host_required: bool = True,
allowed_schemes: Optional[Collection[str]] = None,
quote_plus: bool = False,
) -> Type[AnyUrl]:
# use kwargs then define conf in a dict to aid with IDE type hinting
namespace = dict(
Expand All @@ -575,7 +567,6 @@ def stricturl(
tld_required=tld_required,
host_required=host_required,
allowed_schemes=allowed_schemes,
quote_plus=quote_plus,
)
return type('UrlValue', (AnyUrl,), namespace)

Expand Down
19 changes: 0 additions & 19 deletions tests/test_networks.py
Expand Up @@ -679,31 +679,12 @@ class Model2(BaseModel):
(dict(scheme='ws', user='foo', password='x', host='example.net'), 'ws://foo:x@example.net'),
(dict(scheme='ws', host='example.net', query='a=b', fragment='c=d'), 'ws://example.net?a=b#c=d'),
(dict(scheme='http', host='example.net', port='1234'), 'http://example.net:1234'),
(dict(scheme='http', user='foo@bar', host='example.net'), 'http://foo%40bar@example.net'),
(dict(scheme='http', user='foo', password='a b', host='example.net'), 'http://foo:a%20b@example.net'),
(dict(scheme='http', host='example.net', query='q=foo bar'), 'http://example.net?q=foo%20bar'),
(dict(scheme='http', host='example.net', path="/m&m's"), 'http://example.net/m%26m%27s'),
],
)
def test_build_url(kwargs, expected):
assert AnyUrl(None, **kwargs) == expected


@pytest.mark.parametrize(
'kwargs,expected',
[
(dict(scheme='https', host='example.com', query='query=my query'), 'https://example.com?query=my+query'),
(
dict(scheme='https', host='example.com', user='my name', password='a password'),
'https://my+name:a+password@example.com',
),
(dict(scheme='https', host='example.com', path='/this is a path'), 'https://example.com/this+is+a+path'),
],
)
def test_build_url_quote_plus(kwargs, expected):
assert stricturl(quote_plus=True).build(**kwargs) == expected


@pytest.mark.parametrize(
'kwargs,expected',
[
Expand Down