From 83e3549fca0200f63d594a621d300fc74276f317 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 8 May 2026 10:24:17 -0700 Subject: [PATCH 1/4] Add Python 3.15 urllib.parse updates --- stdlib/@tests/stubtest_allowlists/py315.txt | 8 - stdlib/urllib/parse.pyi | 278 ++++++++++++++++++-- 2 files changed, 250 insertions(+), 36 deletions(-) diff --git a/stdlib/@tests/stubtest_allowlists/py315.txt b/stdlib/@tests/stubtest_allowlists/py315.txt index d76872ab2ebb..952f5cbd8dae 100644 --- a/stdlib/@tests/stubtest_allowlists/py315.txt +++ b/stdlib/@tests/stubtest_allowlists/py315.txt @@ -420,14 +420,6 @@ unicodedata.isxidstart unicodedata.iter_graphemes unittest._log._AssertLogsContext.__init__ unittest.case.TestCase.assertLogs -urllib.parse._DefragResultBase.geturl -urllib.parse._ParseResultBase.geturl -urllib.parse._SplitResultBase.geturl -urllib.parse.urldefrag -urllib.parse.urlparse -urllib.parse.urlsplit -urllib.parse.urlunparse -urllib.parse.urlunsplit warnings.WarningMessage.__init__ wave.WAVE_FORMAT_EXTENSIBLE wave.WAVE_FORMAT_IEEE_FLOAT diff --git a/stdlib/urllib/parse.pyi b/stdlib/urllib/parse.pyi index fe1cd6a71567..5cf8485f658f 100644 --- a/stdlib/urllib/parse.pyi +++ b/stdlib/urllib/parse.pyi @@ -1,4 +1,5 @@ import sys +from _typeshed import Incomplete from collections.abc import Iterable, Mapping, Sequence from types import GenericAlias from typing import Any, AnyStr, Final, Generic, Literal, NamedTuple, Protocol, TypeAlias, overload, type_check_only @@ -63,24 +64,52 @@ class _NetlocResultMixinStr(_NetlocResultMixinBase[str], _ResultMixinStr): class _NetlocResultMixinBytes(_NetlocResultMixinBase[bytes], _ResultMixinBytes): __slots__ = () -class _DefragResultBase(NamedTuple, Generic[AnyStr]): - url: AnyStr - fragment: AnyStr - -class _SplitResultBase(NamedTuple, Generic[AnyStr]): - scheme: AnyStr - netloc: AnyStr - path: AnyStr - query: AnyStr - fragment: AnyStr - -class _ParseResultBase(NamedTuple, Generic[AnyStr]): - scheme: AnyStr - netloc: AnyStr - path: AnyStr - params: AnyStr - query: AnyStr - fragment: AnyStr +if sys.version_info >= (3, 15): + class _DefragResultBase(NamedTuple, Generic[AnyStr]): + url: AnyStr + fragment: AnyStr + def geturl(self) -> AnyStr: ... # type: ignore[misc] + +else: + class _DefragResultBase(NamedTuple, Generic[AnyStr]): + url: AnyStr + fragment: AnyStr + +if sys.version_info >= (3, 15): + class _SplitResultBase(NamedTuple, Generic[AnyStr]): + scheme: AnyStr + netloc: AnyStr + path: AnyStr + query: AnyStr + fragment: AnyStr + def geturl(self) -> AnyStr: ... # type: ignore[misc] + +else: + class _SplitResultBase(NamedTuple, Generic[AnyStr]): + scheme: AnyStr + netloc: AnyStr + path: AnyStr + query: AnyStr + fragment: AnyStr + +if sys.version_info >= (3, 15): + class _ParseResultBase(NamedTuple, Generic[AnyStr]): + scheme: AnyStr + netloc: AnyStr + path: AnyStr + params: AnyStr + query: AnyStr + fragment: AnyStr + def geturl(self) -> AnyStr: ... # type: ignore[misc] + +else: + class _ParseResultBase(NamedTuple, Generic[AnyStr]): + scheme: AnyStr + netloc: AnyStr + path: AnyStr + params: AnyStr + query: AnyStr + fragment: AnyStr # Structured result objects for string data class DefragResult(_DefragResultBase[str], _ResultMixinStr): @@ -102,6 +131,95 @@ class SplitResultBytes(_SplitResultBase[bytes], _NetlocResultMixinBytes): class ParseResultBytes(_ParseResultBase[bytes], _NetlocResultMixinBytes): def geturl(self) -> bytes: ... +if sys.version_info >= (3, 15): + @type_check_only + class DefragResultMaybeNone(NamedTuple): + url: str + fragment: str | None + def encode(self, encoding: str = "ascii", errors: str = "strict") -> DefragResultBytesMaybeNone: ... + def geturl(self) -> str: ... + + @type_check_only + class SplitResultMaybeNone(NamedTuple): + scheme: str | None + netloc: str | None + path: str + query: str | None + fragment: str | None + @property + def username(self) -> str | None: ... + @property + def password(self) -> str | None: ... + @property + def hostname(self) -> str | None: ... + @property + def port(self) -> int | None: ... + def encode(self, encoding: str = "ascii", errors: str = "strict") -> SplitResultBytesMaybeNone: ... + def geturl(self) -> str: ... + + @type_check_only + class ParseResultMaybeNone(NamedTuple): + scheme: str | None + netloc: str | None + path: str + params: str | None + query: str | None + fragment: str | None + @property + def username(self) -> str | None: ... + @property + def password(self) -> str | None: ... + @property + def hostname(self) -> str | None: ... + @property + def port(self) -> int | None: ... + def encode(self, encoding: str = "ascii", errors: str = "strict") -> ParseResultBytesMaybeNone: ... + def geturl(self) -> str: ... + + @type_check_only + class DefragResultBytesMaybeNone(NamedTuple): + url: bytes + fragment: bytes | None + def decode(self, encoding: str = "ascii", errors: str = "strict") -> DefragResultMaybeNone: ... + def geturl(self) -> bytes: ... + + @type_check_only + class SplitResultBytesMaybeNone(NamedTuple): + scheme: bytes | None + netloc: bytes | None + path: bytes + query: bytes | None + fragment: bytes | None + @property + def username(self) -> bytes | None: ... + @property + def password(self) -> bytes | None: ... + @property + def hostname(self) -> bytes | None: ... + @property + def port(self) -> int | None: ... + def decode(self, encoding: str = "ascii", errors: str = "strict") -> SplitResultMaybeNone: ... + def geturl(self) -> bytes: ... + + @type_check_only + class ParseResultBytesMaybeNone(NamedTuple): + scheme: bytes | None + netloc: bytes | None + path: bytes + params: bytes | None + query: bytes | None + fragment: bytes | None + @property + def username(self) -> bytes | None: ... + @property + def password(self) -> bytes | None: ... + @property + def hostname(self) -> bytes | None: ... + @property + def port(self) -> int | None: ... + def decode(self, encoding: str = "ascii", errors: str = "strict") -> ParseResultMaybeNone: ... + def geturl(self) -> bytes: ... + def parse_qs( qs: AnyStr | None, keep_blank_values: bool = False, @@ -137,6 +255,20 @@ def urldefrag(url: str) -> DefragResult: ... @overload def urldefrag(url: bytes | bytearray | None) -> DefragResultBytes: ... +if sys.version_info >= (3, 15): + @overload + def urldefrag(url: str, *, missing_as_none: Literal[True]) -> DefragResultMaybeNone: ... + @overload + def urldefrag(url: str, *, missing_as_none: Literal[False] = False) -> DefragResult: ... + @overload + def urldefrag(url: bytes | bytearray | None, *, missing_as_none: Literal[True]) -> DefragResultBytesMaybeNone: ... + @overload + def urldefrag(url: bytes | bytearray | None, *, missing_as_none: Literal[False] = False) -> DefragResultBytes: ... + @overload + def urldefrag(url: str, *, missing_as_none: bool) -> DefragResult | DefragResultMaybeNone: ... + @overload + def urldefrag(url: bytes | bytearray | None, *, missing_as_none: bool) -> DefragResultBytes | DefragResultBytesMaybeNone: ... + # The values are passed through `str()` (unless they are bytes), so anything is valid. _QueryType: TypeAlias = ( Mapping[str, object] @@ -171,6 +303,45 @@ def urlparse(url: str, scheme: str = "", allow_fragments: bool = True) -> ParseR def urlparse( url: bytes | bytearray | None, scheme: bytes | bytearray | None | Literal[""] = "", allow_fragments: bool = True ) -> ParseResultBytes: ... + +if sys.version_info >= (3, 15): + @overload + def urlparse( + url: str, scheme: str = "", allow_fragments: bool = True, *, missing_as_none: Literal[True] + ) -> ParseResultMaybeNone: ... + @overload + def urlparse( + url: str, scheme: str = "", allow_fragments: bool = True, *, missing_as_none: Literal[False] = False + ) -> ParseResult: ... + @overload + def urlparse( + url: bytes | bytearray | None, + scheme: bytes | bytearray | None | Literal[""] = "", + allow_fragments: bool = True, + *, + missing_as_none: Literal[True], + ) -> ParseResultBytesMaybeNone: ... + @overload + def urlparse( + url: bytes | bytearray | None, + scheme: bytes | bytearray | None | Literal[""] = "", + allow_fragments: bool = True, + *, + missing_as_none: Literal[False] = False, + ) -> ParseResultBytes: ... + @overload + def urlparse( + url: str, scheme: str = "", allow_fragments: bool = True, *, missing_as_none: bool + ) -> ParseResult | ParseResultMaybeNone: ... + @overload + def urlparse( + url: bytes | bytearray | None, + scheme: bytes | bytearray | None | Literal[""] = "", + allow_fragments: bool = True, + *, + missing_as_none: bool, + ) -> ParseResultBytes | ParseResultBytesMaybeNone: ... + @overload def urlsplit(url: str, scheme: str = "", allow_fragments: bool = True) -> SplitResult: ... @@ -186,15 +357,66 @@ else: url: bytes | bytearray | None, scheme: bytes | bytearray | None | Literal[""] = "", allow_fragments: bool = True ) -> SplitResultBytes: ... -# Requires an iterable of length 6 -@overload -def urlunparse(components: Iterable[None]) -> Literal[b""]: ... # type: ignore[overload-overlap] -@overload -def urlunparse(components: Iterable[AnyStr | None]) -> AnyStr: ... +if sys.version_info >= (3, 15): + @overload + def urlsplit( + url: str, scheme: str = "", allow_fragments: bool = True, *, missing_as_none: Literal[True] + ) -> SplitResultMaybeNone: ... + @overload + def urlsplit( + url: str, scheme: str = "", allow_fragments: bool = True, *, missing_as_none: Literal[False] = False + ) -> SplitResult: ... + @overload + def urlsplit( + url: bytes | None, + scheme: bytes | None | Literal[""] = "", + allow_fragments: bool = True, + *, + missing_as_none: Literal[True], + ) -> SplitResultBytesMaybeNone: ... + @overload + def urlsplit( + url: bytes | None, + scheme: bytes | None | Literal[""] = "", + allow_fragments: bool = True, + *, + missing_as_none: Literal[False] = False, + ) -> SplitResultBytes: ... + @overload + def urlsplit( + url: str, scheme: str = "", allow_fragments: bool = True, *, missing_as_none: bool + ) -> SplitResult | SplitResultMaybeNone: ... + @overload + def urlsplit( + url: bytes | None, scheme: bytes | None | Literal[""] = "", allow_fragments: bool = True, *, missing_as_none: bool + ) -> SplitResultBytes | SplitResultBytesMaybeNone: ... + +if sys.version_info >= (3, 15): + # Requires an iterable of length 6 + @overload + def urlunparse(components: Iterable[None], *, keep_empty: bool | None | Incomplete = ...) -> Literal[b""]: ... # type: ignore[overload-overlap] + @overload + def urlunparse(components: Iterable[AnyStr | None], *, keep_empty: bool | None | Incomplete = ...) -> AnyStr: ... + +else: + # Requires an iterable of length 6 + @overload + def urlunparse(components: Iterable[None]) -> Literal[b""]: ... # type: ignore[overload-overlap] + @overload + def urlunparse(components: Iterable[AnyStr | None]) -> AnyStr: ... + +if sys.version_info >= (3, 15): + # Requires an iterable of length 5 + @overload + def urlunsplit(components: Iterable[None], *, keep_empty: bool | None | Incomplete = ...) -> Literal[b""]: ... # type: ignore[overload-overlap] + @overload + def urlunsplit(components: Iterable[AnyStr | None], *, keep_empty: bool | None | Incomplete = ...) -> AnyStr: ... + +else: + # Requires an iterable of length 5 + @overload + def urlunsplit(components: Iterable[None]) -> Literal[b""]: ... # type: ignore[overload-overlap] + @overload + def urlunsplit(components: Iterable[AnyStr | None]) -> AnyStr: ... -# Requires an iterable of length 5 -@overload -def urlunsplit(components: Iterable[None]) -> Literal[b""]: ... # type: ignore[overload-overlap] -@overload -def urlunsplit(components: Iterable[AnyStr | None]) -> AnyStr: ... def unwrap(url: str) -> str: ... From 87938d093113d23120439ce21edd401c37350dd5 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 9 May 2026 10:42:40 -0700 Subject: [PATCH 2/4] amend --- stdlib/urllib/parse.pyi | 241 +++++++++++++++------------------------- 1 file changed, 87 insertions(+), 154 deletions(-) diff --git a/stdlib/urllib/parse.pyi b/stdlib/urllib/parse.pyi index 5cf8485f658f..381c7c1718df 100644 --- a/stdlib/urllib/parse.pyi +++ b/stdlib/urllib/parse.pyi @@ -1,8 +1,8 @@ import sys -from _typeshed import Incomplete from collections.abc import Iterable, Mapping, Sequence from types import GenericAlias from typing import Any, AnyStr, Final, Generic, Literal, NamedTuple, Protocol, TypeAlias, overload, type_check_only +from typing_extensions import TypeVar __all__ = [ "urlparse", @@ -38,6 +38,11 @@ scheme_chars: Final[str] if sys.version_info < (3, 11): MAX_CACHE_SIZE: Final[int] +_ResultStrT = TypeVar("_ResultStrT", str, bytes) +_ResultComponentT = TypeVar("_ResultComponentT", str, bytes, str | None, bytes | None) +_StrComponentT = TypeVar("_StrComponentT", str, str | None, default=str) +_BytesComponentT = TypeVar("_BytesComponentT", bytes, bytes | None, default=bytes) + class _ResultMixinStr: __slots__ = () def encode(self, encoding: str = "ascii", errors: str = "strict") -> _ResultMixinBytes: ... @@ -64,160 +69,88 @@ class _NetlocResultMixinStr(_NetlocResultMixinBase[str], _ResultMixinStr): class _NetlocResultMixinBytes(_NetlocResultMixinBase[bytes], _ResultMixinBytes): __slots__ = () +# Need to duplicate the whole class because mypy rejects version-specific +# branches in namedtuple bodies. if sys.version_info >= (3, 15): - class _DefragResultBase(NamedTuple, Generic[AnyStr]): - url: AnyStr - fragment: AnyStr - def geturl(self) -> AnyStr: ... # type: ignore[misc] + class _DefragResultBase(NamedTuple, Generic[_ResultStrT, _ResultComponentT]): + url: _ResultStrT + fragment: _ResultComponentT + if sys.version_info >= (3, 15): + # Ignore needed due to mypy#21453. + def geturl(self) -> _ResultStrT: ... # type: ignore[misc] else: - class _DefragResultBase(NamedTuple, Generic[AnyStr]): - url: AnyStr - fragment: AnyStr + class _DefragResultBase(NamedTuple, Generic[_ResultStrT, _ResultComponentT]): + url: _ResultStrT + fragment: _ResultComponentT if sys.version_info >= (3, 15): - class _SplitResultBase(NamedTuple, Generic[AnyStr]): - scheme: AnyStr - netloc: AnyStr - path: AnyStr - query: AnyStr - fragment: AnyStr - def geturl(self) -> AnyStr: ... # type: ignore[misc] + class _SplitResultBase(NamedTuple, Generic[_ResultStrT, _ResultComponentT]): + scheme: _ResultComponentT + netloc: _ResultComponentT + path: _ResultStrT + query: _ResultComponentT + fragment: _ResultComponentT + # Ignore needed due to mypy#21453. + def geturl(self) -> _ResultStrT: ... # type: ignore[misc] else: - class _SplitResultBase(NamedTuple, Generic[AnyStr]): - scheme: AnyStr - netloc: AnyStr - path: AnyStr - query: AnyStr - fragment: AnyStr + class _SplitResultBase(NamedTuple, Generic[_ResultStrT, _ResultComponentT]): + scheme: _ResultComponentT + netloc: _ResultComponentT + path: _ResultStrT + query: _ResultComponentT + fragment: _ResultComponentT if sys.version_info >= (3, 15): - class _ParseResultBase(NamedTuple, Generic[AnyStr]): - scheme: AnyStr - netloc: AnyStr - path: AnyStr - params: AnyStr - query: AnyStr - fragment: AnyStr - def geturl(self) -> AnyStr: ... # type: ignore[misc] + class _ParseResultBase(NamedTuple, Generic[_ResultStrT, _ResultComponentT]): + scheme: _ResultComponentT + netloc: _ResultComponentT + path: _ResultStrT + params: _ResultComponentT + query: _ResultComponentT + fragment: _ResultComponentT + # Ignore needed due to mypy#21453. + def geturl(self) -> _ResultStrT: ... # type: ignore[misc] else: - class _ParseResultBase(NamedTuple, Generic[AnyStr]): - scheme: AnyStr - netloc: AnyStr - path: AnyStr - params: AnyStr - query: AnyStr - fragment: AnyStr - -# Structured result objects for string data -class DefragResult(_DefragResultBase[str], _ResultMixinStr): - def geturl(self) -> str: ... - -class SplitResult(_SplitResultBase[str], _NetlocResultMixinStr): - def geturl(self) -> str: ... - -class ParseResult(_ParseResultBase[str], _NetlocResultMixinStr): - def geturl(self) -> str: ... - -# Structured result objects for bytes data -class DefragResultBytes(_DefragResultBase[bytes], _ResultMixinBytes): - def geturl(self) -> bytes: ... - -class SplitResultBytes(_SplitResultBase[bytes], _NetlocResultMixinBytes): - def geturl(self) -> bytes: ... - -class ParseResultBytes(_ParseResultBase[bytes], _NetlocResultMixinBytes): - def geturl(self) -> bytes: ... + class _ParseResultBase(NamedTuple, Generic[_ResultStrT, _ResultComponentT]): + scheme: _ResultComponentT + netloc: _ResultComponentT + path: _ResultStrT + params: _ResultComponentT + query: _ResultComponentT + fragment: _ResultComponentT if sys.version_info >= (3, 15): - @type_check_only - class DefragResultMaybeNone(NamedTuple): - url: str - fragment: str | None - def encode(self, encoding: str = "ascii", errors: str = "strict") -> DefragResultBytesMaybeNone: ... + # Structured result objects for string data + class DefragResult(_DefragResultBase[str, _StrComponentT], _ResultMixinStr, Generic[_StrComponentT]): ... + class SplitResult(_SplitResultBase[str, _StrComponentT], _NetlocResultMixinStr, Generic[_StrComponentT]): ... + class ParseResult(_ParseResultBase[str, _StrComponentT], _NetlocResultMixinStr, Generic[_StrComponentT]): ... + # Structured result objects for bytes data + class DefragResultBytes(_DefragResultBase[bytes, _BytesComponentT], _ResultMixinBytes, Generic[_BytesComponentT]): ... + class SplitResultBytes(_SplitResultBase[bytes, _BytesComponentT], _NetlocResultMixinBytes, Generic[_BytesComponentT]): ... + class ParseResultBytes(_ParseResultBase[bytes, _BytesComponentT], _NetlocResultMixinBytes, Generic[_BytesComponentT]): ... + +else: + # Structured result objects for string data + class DefragResult(_DefragResultBase[str, str], _ResultMixinStr): def geturl(self) -> str: ... - @type_check_only - class SplitResultMaybeNone(NamedTuple): - scheme: str | None - netloc: str | None - path: str - query: str | None - fragment: str | None - @property - def username(self) -> str | None: ... - @property - def password(self) -> str | None: ... - @property - def hostname(self) -> str | None: ... - @property - def port(self) -> int | None: ... - def encode(self, encoding: str = "ascii", errors: str = "strict") -> SplitResultBytesMaybeNone: ... + class SplitResult(_SplitResultBase[str, str], _NetlocResultMixinStr): def geturl(self) -> str: ... - @type_check_only - class ParseResultMaybeNone(NamedTuple): - scheme: str | None - netloc: str | None - path: str - params: str | None - query: str | None - fragment: str | None - @property - def username(self) -> str | None: ... - @property - def password(self) -> str | None: ... - @property - def hostname(self) -> str | None: ... - @property - def port(self) -> int | None: ... - def encode(self, encoding: str = "ascii", errors: str = "strict") -> ParseResultBytesMaybeNone: ... + class ParseResult(_ParseResultBase[str, str], _NetlocResultMixinStr): def geturl(self) -> str: ... - @type_check_only - class DefragResultBytesMaybeNone(NamedTuple): - url: bytes - fragment: bytes | None - def decode(self, encoding: str = "ascii", errors: str = "strict") -> DefragResultMaybeNone: ... + # Structured result objects for bytes data + class DefragResultBytes(_DefragResultBase[bytes, bytes], _ResultMixinBytes): def geturl(self) -> bytes: ... - @type_check_only - class SplitResultBytesMaybeNone(NamedTuple): - scheme: bytes | None - netloc: bytes | None - path: bytes - query: bytes | None - fragment: bytes | None - @property - def username(self) -> bytes | None: ... - @property - def password(self) -> bytes | None: ... - @property - def hostname(self) -> bytes | None: ... - @property - def port(self) -> int | None: ... - def decode(self, encoding: str = "ascii", errors: str = "strict") -> SplitResultMaybeNone: ... + class SplitResultBytes(_SplitResultBase[bytes, bytes], _NetlocResultMixinBytes): def geturl(self) -> bytes: ... - @type_check_only - class ParseResultBytesMaybeNone(NamedTuple): - scheme: bytes | None - netloc: bytes | None - path: bytes - params: bytes | None - query: bytes | None - fragment: bytes | None - @property - def username(self) -> bytes | None: ... - @property - def password(self) -> bytes | None: ... - @property - def hostname(self) -> bytes | None: ... - @property - def port(self) -> int | None: ... - def decode(self, encoding: str = "ascii", errors: str = "strict") -> ParseResultMaybeNone: ... + class ParseResultBytes(_ParseResultBase[bytes, bytes], _NetlocResultMixinBytes): def geturl(self) -> bytes: ... def parse_qs( @@ -257,17 +190,17 @@ def urldefrag(url: bytes | bytearray | None) -> DefragResultBytes: ... if sys.version_info >= (3, 15): @overload - def urldefrag(url: str, *, missing_as_none: Literal[True]) -> DefragResultMaybeNone: ... + def urldefrag(url: str, *, missing_as_none: Literal[True]) -> DefragResult[str | None]: ... @overload - def urldefrag(url: str, *, missing_as_none: Literal[False] = False) -> DefragResult: ... + def urldefrag(url: str, *, missing_as_none: Literal[False] = False) -> DefragResult[str]: ... @overload - def urldefrag(url: bytes | bytearray | None, *, missing_as_none: Literal[True]) -> DefragResultBytesMaybeNone: ... + def urldefrag(url: bytes | bytearray | None, *, missing_as_none: Literal[True]) -> DefragResultBytes[bytes | None]: ... @overload - def urldefrag(url: bytes | bytearray | None, *, missing_as_none: Literal[False] = False) -> DefragResultBytes: ... + def urldefrag(url: bytes | bytearray | None, *, missing_as_none: Literal[False] = False) -> DefragResultBytes[bytes]: ... @overload - def urldefrag(url: str, *, missing_as_none: bool) -> DefragResult | DefragResultMaybeNone: ... + def urldefrag(url: str, *, missing_as_none: bool) -> DefragResult[str | None]: ... @overload - def urldefrag(url: bytes | bytearray | None, *, missing_as_none: bool) -> DefragResultBytes | DefragResultBytesMaybeNone: ... + def urldefrag(url: bytes | bytearray | None, *, missing_as_none: bool) -> DefragResultBytes[bytes | None]: ... # The values are passed through `str()` (unless they are bytes), so anything is valid. _QueryType: TypeAlias = ( @@ -308,11 +241,11 @@ if sys.version_info >= (3, 15): @overload def urlparse( url: str, scheme: str = "", allow_fragments: bool = True, *, missing_as_none: Literal[True] - ) -> ParseResultMaybeNone: ... + ) -> ParseResult[str | None]: ... @overload def urlparse( url: str, scheme: str = "", allow_fragments: bool = True, *, missing_as_none: Literal[False] = False - ) -> ParseResult: ... + ) -> ParseResult[str]: ... @overload def urlparse( url: bytes | bytearray | None, @@ -320,7 +253,7 @@ if sys.version_info >= (3, 15): allow_fragments: bool = True, *, missing_as_none: Literal[True], - ) -> ParseResultBytesMaybeNone: ... + ) -> ParseResultBytes[bytes | None]: ... @overload def urlparse( url: bytes | bytearray | None, @@ -328,11 +261,11 @@ if sys.version_info >= (3, 15): allow_fragments: bool = True, *, missing_as_none: Literal[False] = False, - ) -> ParseResultBytes: ... + ) -> ParseResultBytes[bytes]: ... @overload def urlparse( url: str, scheme: str = "", allow_fragments: bool = True, *, missing_as_none: bool - ) -> ParseResult | ParseResultMaybeNone: ... + ) -> ParseResult[str | None]: ... @overload def urlparse( url: bytes | bytearray | None, @@ -340,7 +273,7 @@ if sys.version_info >= (3, 15): allow_fragments: bool = True, *, missing_as_none: bool, - ) -> ParseResultBytes | ParseResultBytesMaybeNone: ... + ) -> ParseResultBytes[bytes | None]: ... @overload def urlsplit(url: str, scheme: str = "", allow_fragments: bool = True) -> SplitResult: ... @@ -361,11 +294,11 @@ if sys.version_info >= (3, 15): @overload def urlsplit( url: str, scheme: str = "", allow_fragments: bool = True, *, missing_as_none: Literal[True] - ) -> SplitResultMaybeNone: ... + ) -> SplitResult[str | None]: ... @overload def urlsplit( url: str, scheme: str = "", allow_fragments: bool = True, *, missing_as_none: Literal[False] = False - ) -> SplitResult: ... + ) -> SplitResult[str]: ... @overload def urlsplit( url: bytes | None, @@ -373,7 +306,7 @@ if sys.version_info >= (3, 15): allow_fragments: bool = True, *, missing_as_none: Literal[True], - ) -> SplitResultBytesMaybeNone: ... + ) -> SplitResultBytes[bytes | None]: ... @overload def urlsplit( url: bytes | None, @@ -381,22 +314,22 @@ if sys.version_info >= (3, 15): allow_fragments: bool = True, *, missing_as_none: Literal[False] = False, - ) -> SplitResultBytes: ... + ) -> SplitResultBytes[bytes]: ... @overload def urlsplit( url: str, scheme: str = "", allow_fragments: bool = True, *, missing_as_none: bool - ) -> SplitResult | SplitResultMaybeNone: ... + ) -> SplitResult[str | None]: ... @overload def urlsplit( url: bytes | None, scheme: bytes | None | Literal[""] = "", allow_fragments: bool = True, *, missing_as_none: bool - ) -> SplitResultBytes | SplitResultBytesMaybeNone: ... + ) -> SplitResultBytes[bytes | None]: ... if sys.version_info >= (3, 15): # Requires an iterable of length 6 @overload - def urlunparse(components: Iterable[None], *, keep_empty: bool | None | Incomplete = ...) -> Literal[b""]: ... # type: ignore[overload-overlap] + def urlunparse(components: Iterable[None], *, keep_empty: bool = ...) -> Literal[b""]: ... # type: ignore[overload-overlap] @overload - def urlunparse(components: Iterable[AnyStr | None], *, keep_empty: bool | None | Incomplete = ...) -> AnyStr: ... + def urlunparse(components: Iterable[AnyStr | None], *, keep_empty: bool = ...) -> AnyStr: ... else: # Requires an iterable of length 6 @@ -408,9 +341,9 @@ else: if sys.version_info >= (3, 15): # Requires an iterable of length 5 @overload - def urlunsplit(components: Iterable[None], *, keep_empty: bool | None | Incomplete = ...) -> Literal[b""]: ... # type: ignore[overload-overlap] + def urlunsplit(components: Iterable[None], *, keep_empty: bool = ...) -> Literal[b""]: ... # type: ignore[overload-overlap] @overload - def urlunsplit(components: Iterable[AnyStr | None], *, keep_empty: bool | None | Incomplete = ...) -> AnyStr: ... + def urlunsplit(components: Iterable[AnyStr | None], *, keep_empty: bool = ...) -> AnyStr: ... else: # Requires an iterable of length 5 From 30831a7df97a4d9ea3781405540bbe52288a447a Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 9 May 2026 10:43:00 -0700 Subject: [PATCH 3/4] . --- stdlib/urllib/parse.pyi | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/stdlib/urllib/parse.pyi b/stdlib/urllib/parse.pyi index 381c7c1718df..d9ba692bd6e9 100644 --- a/stdlib/urllib/parse.pyi +++ b/stdlib/urllib/parse.pyi @@ -75,9 +75,8 @@ if sys.version_info >= (3, 15): class _DefragResultBase(NamedTuple, Generic[_ResultStrT, _ResultComponentT]): url: _ResultStrT fragment: _ResultComponentT - if sys.version_info >= (3, 15): - # Ignore needed due to mypy#21453. - def geturl(self) -> _ResultStrT: ... # type: ignore[misc] + # Ignore needed due to mypy#21453. + def geturl(self) -> _ResultStrT: ... # type: ignore[misc] else: class _DefragResultBase(NamedTuple, Generic[_ResultStrT, _ResultComponentT]): From bbc0a7ca84145c6dc81f9c9194d315f6a14271e7 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 9 May 2026 10:47:21 -0700 Subject: [PATCH 4/4] ignore --- stdlib/@tests/stubtest_allowlists/py315.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/stdlib/@tests/stubtest_allowlists/py315.txt b/stdlib/@tests/stubtest_allowlists/py315.txt index 4e09bcda11a6..42739c061a1b 100644 --- a/stdlib/@tests/stubtest_allowlists/py315.txt +++ b/stdlib/@tests/stubtest_allowlists/py315.txt @@ -288,3 +288,11 @@ xml.etree.ElementTree.__all__ xml.is_valid_name xml.utils zipimport.zipimporter.load_module + +# ============================================================= +# Allowlist entries that cannot or should not be fixed; >=3.15 +# ============================================================= + +# runtime default is a list object used as a sentinel +urllib.parse.urlunparse +urllib.parse.urlunsplit