Skip to content
25 changes: 25 additions & 0 deletions Doc/library/urllib.parse.rst
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ or on combining URL components into a URL string.
.. versionchanged:: 3.15
Added the *missing_as_none* parameter.

.. versionchanged:: 3.16
Values for ``url`` and ``scheme`` other than strings, bytes, or ``None``
raise :exc:`TypeError` if true or :exc:`DeprecationWarning` if false (to
be changed to :exc:`TypeError` in future versions of Python).

.. _WHATWG spec: https://url.spec.whatwg.org/#concept-basic-url-parser


Expand Down Expand Up @@ -320,6 +325,11 @@ or on combining URL components into a URL string.
query parameter separator. This has been changed to allow only a single
separator key, with ``&`` as the default separator.

.. versionchanged:: 3.16
Values for ``qs`` and ``separator`` other than strings, bytes, or
``None`` raise :exc:`TypeError` if true or :exc:`DeprecationWarning` if
false (to be changed to :exc:`TypeError` in future versions of Python).


.. function:: urlunsplit(parts)
urlunsplit(parts, *, keep_empty)
Expand Down Expand Up @@ -379,6 +389,11 @@ or on combining URL components into a URL string.
.. versionchanged:: 3.15
Added the *keep_empty* parameter.

.. versionchanged:: 3.16
Items in ``parts`` other than strings, bytes, or ``None`` raise
:exc:`TypeError` if true or :exc:`DeprecationWarning` if false (to be
changed to :exc:`TypeError` in future versions of Python).


.. function:: urljoin(base, url, allow_fragments=True)

Expand Down Expand Up @@ -422,6 +437,11 @@ or on combining URL components into a URL string.

Behavior updated to match the semantics defined in :rfc:`3986`.

.. versionchanged:: 3.16
Values for ``base`` and ``url`` other than strings, bytes, or ``None``
raise :exc:`TypeError` if true or :exc:`DeprecationWarning` if false (to
be changed to :exc:`TypeError` in future versions of Python).


.. function:: urldefrag(url, *, missing_as_none=False)

Expand Down Expand Up @@ -452,6 +472,11 @@ or on combining URL components into a URL string.
.. versionchanged:: 3.15
Added the *missing_as_none* parameter.

.. versionchanged:: 3.16
Values other than other than strings, bytes, or ``None`` raise
:exc:`TypeError` if true or :exc:`DeprecationWarning` if false (to be
changed to :exc:`TypeError` in future versions of Python).

.. function:: unwrap(url)

Extract the url from a wrapped URL (that is, a string formatted as
Expand Down
8 changes: 8 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,14 @@ New deprecations
open them one by one instead.
(Contributed by Serhiy Storchaka in :gh:`152638`.)

* :mod:`urllib`:

* Providing anything but a string, bytes object, or ``None`` to
:mod:`urllib.parse` functions expecting strings or bytes now raises
:exc:`DeprecationWarning` if the value tests false, or :exc:`TypeError` if
it tests true.
(Contributed by Jacob Walls in :issue:`19094`.)

.. Add deprecations above alphabetically, not here at the end.
.. include:: ../deprecations/pending-removal-in-3.17.rst
Expand Down
23 changes: 23 additions & 0 deletions Lib/test/test_urlparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -1255,6 +1255,12 @@ def test_mixed_types_rejected(self):
with self.assertRaisesRegex(TypeError, "Cannot mix str"):
urllib.parse.urljoin(b"http://python.org", "http://python.org")

def test_non_string_true_values_rejected(self):
# True values raise informative TypeErrors
msg = "Expected a string, bytes, or None: got <class "
with self.assertRaisesRegex(TypeError, msg):
urllib.parse.urlsplit(1, b'http')

def _check_result_type(self, str_type, str_args):
bytes_type = str_type._encoded_counterpart
self.assertIs(bytes_type._decoded_counterpart, str_type)
Expand Down Expand Up @@ -1992,6 +1998,23 @@ def test_to_bytes_deprecation(self):
self.assertEqual(str(cm.warning),
'urllib.parse.to_bytes() is deprecated as of 3.8')

def test_falsey_deprecation(self):
cases = [
(urllib.parse.urljoin, ['http://www.python.org', []]),
(urllib.parse.urljoin, [[], b'docs']),
(urllib.parse.urlparse, [b'www.python.org', {}]),
(urllib.parse.urlparse, [{}, '']),
(urllib.parse.urlsplit, [0, b'http']),
(urllib.parse.urlsplit, [b'http://www.python.org', 0]),
(urllib.parse.urldefrag, [{}]),
(urllib.parse.urlunparse, [[None, b'www.python.org', (), (), (), ()]]),
(urllib.parse.urlunsplit, [['http', 0, '', '', '']]),
]
for func, args in cases:
with self.subTest(callable=func.__name__, args=args):
with self.assertWarnsRegex(DeprecationWarning, "false values"):
func(*args)


def str_encode(s):
if s is None:
Expand Down
13 changes: 10 additions & 3 deletions Lib/urllib/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,13 @@ def _coerce_args(*args):
else:
if isinstance(arg, str) != str_input:
raise TypeError("Cannot mix str and non-str arguments")
if not hasattr(arg, "decode") and str_input is False:
raise TypeError(f"Expected a string, bytes, or None: got {type(arg)}")
elif arg is not None and not isinstance(arg, str) and not hasattr(arg, "decode"):
warnings.warn(
f"Providing false values other than empty strings, bytes, or"
f"None to urllib.parse is deprecated: got {type(arg)}",
DeprecationWarning, stacklevel=3)
if str_input is None:
for arg in args:
if arg is not None:
Expand Down Expand Up @@ -697,12 +704,12 @@ def _urlunsplit(scheme, netloc, url, query, fragment):
def urljoin(base, url, allow_fragments=True):
"""Join a base URL and a possibly relative URL to form an absolute
interpretation of the latter."""
base, url, _coerce_result = _coerce_args(base, url)
if not base:
return url
return _coerce_result(url)
if not url:
return base
return _coerce_result(base)

base, url, _coerce_result = _coerce_args(base, url)
bscheme, bnetloc, bpath, bquery, bfragment = \
_urlsplit(base, None, allow_fragments)
scheme, netloc, path, query, fragment = \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Providing values other than other than strings, bytes, or ``None`` to
:mod:`urllib.parse` functions expecting strings or bytes now raises
:exc:`TypeError` if true or :exc:`DeprecationWarning` if false (to be
changed to :exc:`TypeError` in future versions of Python).
Loading