From 3a4ad2e5451e2d00bab0607e7ec73783a4513a1a Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Thu, 26 Mar 2026 21:01:01 -0400 Subject: [PATCH 1/3] [gunicorn] Update to 25.3.0 --- stubs/gunicorn/METADATA.toml | 2 +- stubs/gunicorn/gunicorn/asgi/__init__.pyi | 3 +- stubs/gunicorn/gunicorn/asgi/message.pyi | 50 ----------------------- stubs/gunicorn/gunicorn/asgi/parser.pyi | 42 ++++++++++++++++++- stubs/gunicorn/gunicorn/http/errors.pyi | 3 ++ 5 files changed, 46 insertions(+), 54 deletions(-) delete mode 100644 stubs/gunicorn/gunicorn/asgi/message.pyi diff --git a/stubs/gunicorn/METADATA.toml b/stubs/gunicorn/METADATA.toml index bb712638260e..ef9dc009f948 100644 --- a/stubs/gunicorn/METADATA.toml +++ b/stubs/gunicorn/METADATA.toml @@ -1,4 +1,4 @@ -version = "25.2.0" +version = "25.3.0" upstream_repository = "https://github.com/benoitc/gunicorn" requires = ["types-gevent"] diff --git a/stubs/gunicorn/gunicorn/asgi/__init__.pyi b/stubs/gunicorn/gunicorn/asgi/__init__.pyi index 7469610eac93..0e635cb1217d 100644 --- a/stubs/gunicorn/gunicorn/asgi/__init__.pyi +++ b/stubs/gunicorn/gunicorn/asgi/__init__.pyi @@ -1,5 +1,4 @@ from gunicorn.asgi.lifespan import LifespanManager as LifespanManager -from gunicorn.asgi.message import AsyncRequest as AsyncRequest from gunicorn.asgi.unreader import AsyncUnreader as AsyncUnreader -__all__ = ["AsyncUnreader", "AsyncRequest", "LifespanManager"] +__all__ = ["AsyncUnreader", "LifespanManager"] diff --git a/stubs/gunicorn/gunicorn/asgi/message.pyi b/stubs/gunicorn/gunicorn/asgi/message.pyi deleted file mode 100644 index a279e34cdd91..000000000000 --- a/stubs/gunicorn/gunicorn/asgi/message.pyi +++ /dev/null @@ -1,50 +0,0 @@ -import re -from typing import Final, Literal -from typing_extensions import Self - -from gunicorn.asgi.unreader import AsyncUnreader -from gunicorn.config import Config - -from .._types import _AddressType - -MAX_REQUEST_LINE: Final = 8190 -MAX_HEADERS: Final = 32768 -DEFAULT_MAX_HEADERFIELD_SIZE: Final = 8190 -RFC9110_5_6_2_TOKEN_SPECIALS: Final = r"!#$%&'*+-.^_`|~" -TOKEN_RE: Final[re.Pattern[str]] -METHOD_BADCHAR_RE: Final[re.Pattern[str]] -VERSION_RE: Final[re.Pattern[str]] -RFC9110_5_5_INVALID_AND_DANGEROUS: Final[re.Pattern[str]] - -class AsyncRequest: - cfg: Config - unreader: AsyncUnreader - peer_addr: _AddressType - remote_addr: _AddressType - req_number: int - version: tuple[int, int] | None - method: str | None - uri: str | None - path: str | None - query: str | None - fragment: str | None - headers: list[tuple[str, str]] - trailers: list[tuple[str, str]] - scheme: Literal["https", "http"] - must_close: bool - proxy_protocol_info: dict[str, str | int | None] | None # TODO: Use TypedDict - limit_request_line: int - limit_request_fields: int - limit_request_field_size: int - max_buffer_headers: int - content_length: int | None - chunked: bool - - def __init__(self, cfg: Config, unreader: AsyncUnreader, peer_addr: _AddressType, req_number: int = 1) -> None: ... - @classmethod - async def parse(cls, cfg: Config, unreader: AsyncUnreader, peer_addr: _AddressType, req_number: int = 1) -> Self: ... - def force_close(self) -> None: ... - def should_close(self) -> bool: ... - def get_header(self, name: str) -> str | None: ... - async def read_body(self, size: int = 8192) -> bytes: ... - async def drain_body(self) -> None: ... diff --git a/stubs/gunicorn/gunicorn/asgi/parser.pyi b/stubs/gunicorn/gunicorn/asgi/parser.pyi index 4ef818bdbf53..a79d6fb21568 100644 --- a/stubs/gunicorn/gunicorn/asgi/parser.pyi +++ b/stubs/gunicorn/gunicorn/asgi/parser.pyi @@ -1,16 +1,49 @@ from collections.abc import Callable, Iterable -from typing import Any, Literal, SupportsIndex +from enum import IntEnum +from typing import Any, Final, Literal, SupportsIndex, TypedDict, type_check_only from typing_extensions import Self, TypeAlias _H1CProtocol: TypeAlias = Any # gunicorn_h1c H1CProtocol class class ParseError(Exception): ... +class InvalidProxyLine(ParseError): ... +class InvalidProxyHeader(ParseError): ... + +PP_V2_SIGNATURE: Final[bytes] + +class PPCommand(IntEnum): + LOCAL = 0x0 + PROXY = 0x1 + +class PPFamily(IntEnum): + UNSPEC = 0x0 + INET = 0x1 + INET6 = 0x2 + UNIX = 0x3 + +class PPProtocol(IntEnum): + UNSPEC = 0x0 + STREAM = 0x1 + DGRAM = 0x2 + class LimitRequestLine(ParseError): ... +class InvalidRequestLine(ParseError): ... class LimitRequestHeaders(ParseError): ... class InvalidRequestMethod(ParseError): ... class InvalidHTTPVersion(ParseError): ... class InvalidHeaderName(ParseError): ... class InvalidHeader(ParseError): ... +class UnsupportedTransferCoding(ParseError): ... +class InvalidChunkSize(ParseError): ... +class InvalidChunkExtension(ParseError): ... + +@type_check_only +class _ProxyProtocolInfo(TypedDict): + proxy_protocol: str + client_addr: str | None + client_port: int | None + proxy_addr: str | None + proxy_port: int | None class PythonProtocol: __slots__ = ( @@ -42,6 +75,9 @@ class PythonProtocol: "_permit_unconventional_http_method", "_permit_unconventional_http_version", "_header_count", + "_proxy_protocol", + "_proxy_protocol_info", + "_proxy_protocol_done", ) method: bytes | None path: bytes | None @@ -65,9 +101,13 @@ class PythonProtocol: limit_request_field_size: int = 8190, permit_unconventional_http_method: bool = False, permit_unconventional_http_version: bool = False, + proxy_protocol: Literal["off", "v1", "v2", "auto"] = "off", ) -> None: ... def feed(self, data: Iterable[SupportsIndex]) -> None: ... + @property + def proxy_protocol_info(self) -> _ProxyProtocolInfo | None: ... def reset(self) -> None: ... + def finish(self) -> None: ... class CallbackRequest: __slots__ = ( diff --git a/stubs/gunicorn/gunicorn/http/errors.pyi b/stubs/gunicorn/gunicorn/http/errors.pyi index 22a57d6087e1..3a2e72b9bfa1 100644 --- a/stubs/gunicorn/gunicorn/http/errors.pyi +++ b/stubs/gunicorn/gunicorn/http/errors.pyi @@ -68,6 +68,9 @@ class ChunkMissingTerminator(IOError): def __init__(self, term: bytes) -> None: ... +class InvalidChunkExtension(IOError): + def __init__(self, reason: str) -> None: ... + class LimitRequestLine(ParseException): size: int max_size: int | None From 1d2e236963a0c8159e5c41b4a0af3367fc4c03cf Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Thu, 26 Mar 2026 21:12:30 -0400 Subject: [PATCH 2/3] Allow better narrowing for proxy protocol fields --- stubs/gunicorn/gunicorn/asgi/parser.pyi | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/stubs/gunicorn/gunicorn/asgi/parser.pyi b/stubs/gunicorn/gunicorn/asgi/parser.pyi index a79d6fb21568..f36970b1c8a5 100644 --- a/stubs/gunicorn/gunicorn/asgi/parser.pyi +++ b/stubs/gunicorn/gunicorn/asgi/parser.pyi @@ -39,11 +39,19 @@ class InvalidChunkExtension(ParseError): ... @type_check_only class _ProxyProtocolInfo(TypedDict): - proxy_protocol: str - client_addr: str | None - client_port: int | None - proxy_addr: str | None - proxy_port: int | None + proxy_protocol: Literal["TCP4", "TCP6", "UDP4", "UDP6"] + client_addr: str + client_port: int + proxy_addr: str + proxy_port: int + +@type_check_only +class _ProxyProtocolInfoUnknown(TypedDict): + proxy_protocol: Literal["UNKNOWN", "LOCAL", "UNSPEC"] + client_addr: None + client_port: None + proxy_addr: None + proxy_port: None class PythonProtocol: __slots__ = ( @@ -105,7 +113,7 @@ class PythonProtocol: ) -> None: ... def feed(self, data: Iterable[SupportsIndex]) -> None: ... @property - def proxy_protocol_info(self) -> _ProxyProtocolInfo | None: ... + def proxy_protocol_info(self) -> _ProxyProtocolInfo | _ProxyProtocolInfoUnknown | None: ... def reset(self) -> None: ... def finish(self) -> None: ... From 8c5a503945b6b95024ff39e4f63fafdfa7ec0f21 Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Fri, 27 Mar 2026 07:39:08 -0400 Subject: [PATCH 3/3] Update stubs/gunicorn/gunicorn/http/errors.pyi Co-authored-by: Sebastian Rittau --- stubs/gunicorn/gunicorn/http/errors.pyi | 1 + 1 file changed, 1 insertion(+) diff --git a/stubs/gunicorn/gunicorn/http/errors.pyi b/stubs/gunicorn/gunicorn/http/errors.pyi index 3a2e72b9bfa1..a7698a0973bc 100644 --- a/stubs/gunicorn/gunicorn/http/errors.pyi +++ b/stubs/gunicorn/gunicorn/http/errors.pyi @@ -69,6 +69,7 @@ class ChunkMissingTerminator(IOError): def __init__(self, term: bytes) -> None: ... class InvalidChunkExtension(IOError): + reason: str def __init__(self, reason: str) -> None: ... class LimitRequestLine(ParseException):