From 039402660f645bd293ca537f36c3df02d36a6b32 Mon Sep 17 00:00:00 2001 From: "Paul J. Dorn" Date: Sun, 26 Oct 2025 00:24:57 +0200 Subject: [PATCH 1/4] gunicorn, draft --- stubs/gunicorn/gunicorn/_types.pyi | 10 +++++++--- stubs/gunicorn/gunicorn/debug.pyi | 2 +- stubs/gunicorn/gunicorn/http/wsgi.pyi | 4 ++-- stubs/gunicorn/gunicorn/util.pyi | 10 +++++----- stubs/gunicorn/gunicorn/workers/base.pyi | 4 ++-- stubs/gunicorn/gunicorn/workers/base_async.pyi | 5 +++-- stubs/gunicorn/gunicorn/workers/geventlet.pyi | 3 ++- stubs/gunicorn/gunicorn/workers/ggevent.pyi | 6 ++++-- stubs/gunicorn/gunicorn/workers/gthread.pyi | 4 ++-- stubs/gunicorn/gunicorn/workers/sync.pyi | 6 +++--- 10 files changed, 31 insertions(+), 23 deletions(-) diff --git a/stubs/gunicorn/gunicorn/_types.pyi b/stubs/gunicorn/gunicorn/_types.pyi index cbe38ddfd655..ec2d2c2fb22b 100644 --- a/stubs/gunicorn/gunicorn/_types.pyi +++ b/stubs/gunicorn/gunicorn/_types.pyi @@ -1,14 +1,18 @@ ### This .pyi file is a helper for centralized storage types that are reused across different runtime modules. ### -from _typeshed import FileDescriptor +from _typeshed import FileDescriptor, OptExcInfo from collections.abc import Awaitable, Callable, Iterable, MutableMapping -from typing import Any +from typing import Any, Protocol from typing_extensions import LiteralString, TypeAlias _StatusType: TypeAlias = str _HeadersType: TypeAlias = Iterable[tuple[str, str]] _EnvironType: TypeAlias = MutableMapping[str, Any] # See https://peps.python.org/pep-0333/ -_StartResponseType: TypeAlias = Callable[[_StatusType, _HeadersType], None] +class _StartResponseType(Protocol): + def __call__( + self, status: _StatusType, headers: _HeadersType, exc_info: OptExcInfo | None = ..., / + ) -> Callable[[bytes], object]: ... + _ResponseBodyType: TypeAlias = Iterable[bytes] _WSGIAppType: TypeAlias = Callable[[_EnvironType, _StartResponseType], _ResponseBodyType] # noqa: Y047 diff --git a/stubs/gunicorn/gunicorn/debug.pyi b/stubs/gunicorn/gunicorn/debug.pyi index 8769f873c587..f0fcbbeb8df9 100644 --- a/stubs/gunicorn/gunicorn/debug.pyi +++ b/stubs/gunicorn/gunicorn/debug.pyi @@ -11,7 +11,7 @@ class Spew: def __init__(self, trace_names: Container[str] | None = None, show_values: bool = True) -> None: ... def __call__( - self, frame: FrameType, event: str, arg: Any # `arg` is not used inside the function, stub is set Any + self, frame: FrameType, event: Literal["call", "line", "return", "exception", "opcode"], arg: Any # `arg` is not used inside the function, stub is set Any ) -> Self: ... def spew(trace_names: Container[str] | None = None, show_values: bool = False) -> None: ... diff --git a/stubs/gunicorn/gunicorn/http/wsgi.pyi b/stubs/gunicorn/gunicorn/http/wsgi.pyi index be6037952d5e..e1a840f81403 100644 --- a/stubs/gunicorn/gunicorn/http/wsgi.pyi +++ b/stubs/gunicorn/gunicorn/http/wsgi.pyi @@ -4,7 +4,7 @@ import re import socket from _typeshed import ReadableBuffer from collections.abc import Callable -from typing import Any +from typing import Any, Never from gunicorn.config import Config from gunicorn.http import Request @@ -21,7 +21,7 @@ class FileWrapper: close: Callable[[], None] | None def __init__(self, filelike: io.IOBase, blksize: int = 8192) -> None: ... - def __getitem__(self, key: Any) -> bytes: ... + def __getitem__(self, key: Never) -> bytes: ... class WSGIErrorsWrapper(io.RawIOBase): streams: list[io.TextIOBase] diff --git a/stubs/gunicorn/gunicorn/util.pyi b/stubs/gunicorn/gunicorn/util.pyi index 5ec09516cb2f..6772a36ea3ce 100644 --- a/stubs/gunicorn/gunicorn/util.pyi +++ b/stubs/gunicorn/gunicorn/util.pyi @@ -12,8 +12,8 @@ hop_headers: set[str] def load_entry_point(distribution: str, group: str, name: str) -> type[object]: ... def load_class( - uri: str | object, default: str = "gunicorn.workers.sync.SyncWorker", section: str = "gunicorn.workers" -) -> type[Any]: ... + uri: str | type[Logger] | type[SyncWorker], default: str = "gunicorn.workers.sync.SyncWorker", section: str = "gunicorn.workers" +) -> type[Logger] | type[SyncWorker]: ... positionals: tuple[Literal[_ParameterKind.POSITIONAL_ONLY], Literal[_ParameterKind.POSITIONAL_OR_KEYWORD]] @@ -30,7 +30,7 @@ def close(sock: socket) -> None: ... def write_chunk(sock: socket, data: bytes) -> None: ... def write(sock: socket, data: bytes, chunked: bool = False) -> None: ... def write_nonblock(sock: socket, data: bytes, chunked: bool = False) -> None: ... -def write_error(sock: socket, status_int: int, reason: str, mesg: str) -> None: ... +def write_error(sock: socket, status_int: int, reason: LiteralString, mesg: str) -> None: ... def import_app(module: str) -> _WSGIAppType: ... def getcwd() -> str: ... def http_date(timestamp: float | None = None) -> str: ... @@ -38,11 +38,11 @@ def is_hoppish(header: str) -> bool: ... def daemonize(enable_stdio_inheritance: bool = False) -> None: ... def seed() -> None: ... def check_is_writable(path: FileDescriptorOrPath) -> None: ... -def to_bytestring(value: str | bytes, encoding: str = "utf8") -> bytes: ... +def to_bytestring(value: str | bytes, encoding: LiteralString = "utf8") -> bytes: ... def has_fileno(obj: HasFileno) -> bool: ... def warn(msg: str) -> None: ... def make_fail_app(msg: str) -> _WSGIAppType: ... def split_request_uri(uri: str) -> SplitResult: ... def reraise(tp: type[BaseException], value: BaseException | None, tb: types.TracebackType | None = None) -> NoReturn: ... -def bytes_to_str(b: bytes) -> str: ... +def bytes_to_str(b: bytes | str) -> str: ... def unquote_to_wsgi_str(string: str) -> str: ... diff --git a/stubs/gunicorn/gunicorn/workers/base.pyi b/stubs/gunicorn/gunicorn/workers/base.pyi index aa0d5fc3a76a..71c6f000cd1d 100644 --- a/stubs/gunicorn/gunicorn/workers/base.pyi +++ b/stubs/gunicorn/gunicorn/workers/base.pyi @@ -19,7 +19,7 @@ class Worker: ppid: int sockets: list[socket.socket] app: BaseApplication - timeout: int + timeout: float cfg: Config booted: bool aborted: bool @@ -33,7 +33,7 @@ class Worker: wsgi: _WSGIAppType def __init__( - self, age: int, ppid: int, sockets: list[socket.socket], app: BaseApplication, timeout: int, cfg: Config, log: GLogger + self, age: int, ppid: int, sockets: list[socket.socket], app: BaseApplication, timeout: float, cfg: Config, log: GLogger ) -> None: ... def notify(self) -> None: ... def run(self) -> None: ... diff --git a/stubs/gunicorn/gunicorn/workers/base_async.pyi b/stubs/gunicorn/gunicorn/workers/base_async.pyi index 6b8523770077..8ddf665e6bdb 100644 --- a/stubs/gunicorn/gunicorn/workers/base_async.pyi +++ b/stubs/gunicorn/gunicorn/workers/base_async.pyi @@ -1,4 +1,5 @@ import socket +from contextlib import AbstractAsyncContextManager from gunicorn.http import Request from gunicorn.workers import base @@ -11,7 +12,7 @@ class AsyncWorker(base.Worker): worker_connections: int alive: bool - def timeout_ctx(self) -> None: ... + def timeout_ctx(self) -> AbstractAsyncContextManager[None]: ... def is_already_handled(self, respiter: object) -> bool: ... def handle(self, listener: socket.socket, client: socket.socket, addr: _AddressType) -> None: ... - def handle_request(self, listener_name: str, req: Request, sock: socket.socket, addr: _AddressType) -> bool: ... + def handle_request(self, listener_name: str, req: Request, sock: socket.socket, addr: _AddressType) -> bool | None: ... diff --git a/stubs/gunicorn/gunicorn/workers/geventlet.pyi b/stubs/gunicorn/gunicorn/workers/geventlet.pyi index 481996a13837..61b4ddc0939c 100644 --- a/stubs/gunicorn/gunicorn/workers/geventlet.pyi +++ b/stubs/gunicorn/gunicorn/workers/geventlet.pyi @@ -1,3 +1,4 @@ +from contextlib import AbstractAsyncContextManager from types import FrameType from typing import Any from typing_extensions import TypeAlias @@ -19,6 +20,6 @@ class EventletWorker(AsyncWorker): def init_process(self) -> None: ... def handle_quit(self, sig: int, frame: FrameType | None) -> None: ... def handle_usr1(self, sig: int, frame: FrameType | None) -> None: ... - def timeout_ctx(self) -> None: ... + def timeout_ctx(self) -> AbstractAsyncContextManager[None]: ... def handle(self, listener: GreenSocket, client: GreenSocket, addr: _AddressType) -> None: ... def run(self) -> None: ... diff --git a/stubs/gunicorn/gunicorn/workers/ggevent.pyi b/stubs/gunicorn/gunicorn/workers/ggevent.pyi index 2fa479231291..6f530fc3b988 100644 --- a/stubs/gunicorn/gunicorn/workers/ggevent.pyi +++ b/stubs/gunicorn/gunicorn/workers/ggevent.pyi @@ -1,3 +1,5 @@ +from contextlib import AbstractAsyncContextManager + from types import FrameType from typing import Any, ClassVar @@ -19,10 +21,10 @@ class GeventWorker(AsyncWorker): def patch(self) -> None: ... def notify(self) -> None: ... - def timeout_ctx(self) -> None: ... + def timeout_ctx(self) -> AbstractAsyncContextManager[None]: ... def run(self) -> None: ... def handle(self, listener: GeventSocket, client: GeventSocket, addr: _AddressType) -> None: ... - def handle_request(self, listener_name: str, req: Request, sock: GeventSocket, addr: _AddressType) -> bool: ... + def handle_request(self, listener_name: _AddressType, req: Request, sock: GeventSocket, addr: _AddressType) -> bool: ... def handle_quit(self, sig: int, frame: FrameType | None) -> None: ... def handle_usr1(self, sig: int, frame: FrameType | None) -> None: ... def init_process(self) -> None: ... diff --git a/stubs/gunicorn/gunicorn/workers/gthread.pyi b/stubs/gunicorn/gunicorn/workers/gthread.pyi index c3057c7e26b5..a083995266bd 100644 --- a/stubs/gunicorn/gunicorn/workers/gthread.pyi +++ b/stubs/gunicorn/gunicorn/workers/gthread.pyi @@ -1,7 +1,7 @@ import socket from collections import deque from concurrent.futures import Future, ThreadPoolExecutor -from selectors import DefaultSelector +from selectors import BaseSelector from types import FrameType from gunicorn.config import Config @@ -29,7 +29,7 @@ class ThreadWorker(base.Worker): worker_connections: int max_keepalived: int tpool: ThreadPoolExecutor - poller: DefaultSelector + poller: BaseSelector futures: deque[Future[tuple[bool, TConn]]] nr_conns: int alive: bool diff --git a/stubs/gunicorn/gunicorn/workers/sync.pyi b/stubs/gunicorn/gunicorn/workers/sync.pyi index cf461d5e785c..2ad45daa14f7 100644 --- a/stubs/gunicorn/gunicorn/workers/sync.pyi +++ b/stubs/gunicorn/gunicorn/workers/sync.pyi @@ -9,10 +9,10 @@ class StopWaiting(Exception): ... class SyncWorker(Worker): def accept(self, listener: socket.socket) -> None: ... - def wait(self, timeout: int) -> list[socket.socket | int] | None: ... + def wait(self, timeout: float) -> list[socket.socket]: ... def is_parent_alive(self) -> bool: ... - def run_for_one(self, timeout: int) -> None: ... - def run_for_multiple(self, timeout: int) -> None: ... + def run_for_one(self, timeout: float) -> None: ... + def run_for_multiple(self, timeout: float) -> None: ... def run(self) -> None: ... def handle(self, listener: socket.socket, client: socket.socket, addr: _AddressType) -> None: ... def handle_request(self, listener: socket.socket, req: Request, client: socket.socket, addr: tuple[str, int]) -> bool: ... From 83585e611d257baa2b2772d769cb9ff1a7be3784 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 25 Oct 2025 22:46:57 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks --- stubs/gunicorn/gunicorn/_types.pyi | 1 + stubs/gunicorn/gunicorn/debug.pyi | 5 ++++- stubs/gunicorn/gunicorn/util.pyi | 6 +++--- stubs/gunicorn/gunicorn/workers/ggevent.pyi | 1 - 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/stubs/gunicorn/gunicorn/_types.pyi b/stubs/gunicorn/gunicorn/_types.pyi index ec2d2c2fb22b..43ca1ad9f9ef 100644 --- a/stubs/gunicorn/gunicorn/_types.pyi +++ b/stubs/gunicorn/gunicorn/_types.pyi @@ -8,6 +8,7 @@ _StatusType: TypeAlias = str _HeadersType: TypeAlias = Iterable[tuple[str, str]] _EnvironType: TypeAlias = MutableMapping[str, Any] # See https://peps.python.org/pep-0333/ + class _StartResponseType(Protocol): def __call__( self, status: _StatusType, headers: _HeadersType, exc_info: OptExcInfo | None = ..., / diff --git a/stubs/gunicorn/gunicorn/debug.pyi b/stubs/gunicorn/gunicorn/debug.pyi index f0fcbbeb8df9..fc1a898d2530 100644 --- a/stubs/gunicorn/gunicorn/debug.pyi +++ b/stubs/gunicorn/gunicorn/debug.pyi @@ -11,7 +11,10 @@ class Spew: def __init__(self, trace_names: Container[str] | None = None, show_values: bool = True) -> None: ... def __call__( - self, frame: FrameType, event: Literal["call", "line", "return", "exception", "opcode"], arg: Any # `arg` is not used inside the function, stub is set Any + self, + frame: FrameType, + event: Literal[call, line, "return", exception, opcode], + arg: Any, # `arg` is not used inside the function, stub is set Any ) -> Self: ... def spew(trace_names: Container[str] | None = None, show_values: bool = False) -> None: ... diff --git a/stubs/gunicorn/gunicorn/util.pyi b/stubs/gunicorn/gunicorn/util.pyi index 6772a36ea3ce..f71bf1f9fa65 100644 --- a/stubs/gunicorn/gunicorn/util.pyi +++ b/stubs/gunicorn/gunicorn/util.pyi @@ -2,7 +2,7 @@ import types from _typeshed import FileDescriptorLike, FileDescriptorOrPath, HasFileno, StrOrBytesPath from inspect import _IntrospectableCallable, _ParameterKind from socket import socket -from typing import Any, Literal, NoReturn +from typing import Literal, NoReturn from urllib.parse import SplitResult from ._types import _AddressType, _WSGIAppType @@ -12,8 +12,8 @@ hop_headers: set[str] def load_entry_point(distribution: str, group: str, name: str) -> type[object]: ... def load_class( - uri: str | type[Logger] | type[SyncWorker], default: str = "gunicorn.workers.sync.SyncWorker", section: str = "gunicorn.workers" -) -> type[Logger] | type[SyncWorker]: ... + uri: type[Logger | SyncWorker] | str, default: str = "gunicorn.workers.sync.SyncWorker", section: str = "gunicorn.workers" +) -> type[Logger | SyncWorker]: ... positionals: tuple[Literal[_ParameterKind.POSITIONAL_ONLY], Literal[_ParameterKind.POSITIONAL_OR_KEYWORD]] diff --git a/stubs/gunicorn/gunicorn/workers/ggevent.pyi b/stubs/gunicorn/gunicorn/workers/ggevent.pyi index 6f530fc3b988..3c46aa4b892e 100644 --- a/stubs/gunicorn/gunicorn/workers/ggevent.pyi +++ b/stubs/gunicorn/gunicorn/workers/ggevent.pyi @@ -1,5 +1,4 @@ from contextlib import AbstractAsyncContextManager - from types import FrameType from typing import Any, ClassVar From 073ec35318e3e047e1172ccc6f18e2cf5b2e6cc5 Mon Sep 17 00:00:00 2001 From: "Paul J. Dorn" Date: Sun, 26 Oct 2025 01:03:55 +0200 Subject: [PATCH 3/4] autofix this --- stubs/gunicorn/gunicorn/debug.pyi | 4 ++-- stubs/gunicorn/gunicorn/http/wsgi.pyi | 3 ++- stubs/gunicorn/gunicorn/util.pyi | 8 ++++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/stubs/gunicorn/gunicorn/debug.pyi b/stubs/gunicorn/gunicorn/debug.pyi index fc1a898d2530..c035d10dd323 100644 --- a/stubs/gunicorn/gunicorn/debug.pyi +++ b/stubs/gunicorn/gunicorn/debug.pyi @@ -2,7 +2,7 @@ __all__ = ["spew", "unspew"] from collections.abc import Container from types import FrameType -from typing import Any +from typing import Any, Literal from typing_extensions import Self class Spew: @@ -13,7 +13,7 @@ class Spew: def __call__( self, frame: FrameType, - event: Literal[call, line, "return", exception, opcode], + event: Literal["call"] | Literal["line"] | Literal["return"] | Literal["exception"] | Literal["opcode"], arg: Any, # `arg` is not used inside the function, stub is set Any ) -> Self: ... diff --git a/stubs/gunicorn/gunicorn/http/wsgi.pyi b/stubs/gunicorn/gunicorn/http/wsgi.pyi index e1a840f81403..2688945b9866 100644 --- a/stubs/gunicorn/gunicorn/http/wsgi.pyi +++ b/stubs/gunicorn/gunicorn/http/wsgi.pyi @@ -4,7 +4,8 @@ import re import socket from _typeshed import ReadableBuffer from collections.abc import Callable -from typing import Any, Never +from typing import Any +from typing_extensions import Never from gunicorn.config import Config from gunicorn.http import Request diff --git a/stubs/gunicorn/gunicorn/util.pyi b/stubs/gunicorn/gunicorn/util.pyi index f71bf1f9fa65..4080a95cd452 100644 --- a/stubs/gunicorn/gunicorn/util.pyi +++ b/stubs/gunicorn/gunicorn/util.pyi @@ -3,8 +3,12 @@ from _typeshed import FileDescriptorLike, FileDescriptorOrPath, HasFileno, StrOr from inspect import _IntrospectableCallable, _ParameterKind from socket import socket from typing import Literal, NoReturn +from typing_extensions import LiteralString from urllib.parse import SplitResult +from gunicorn.glogging import Logger +from gunicorn.workers.base import Worker + from ._types import _AddressType, _WSGIAppType REDIRECT_TO: str @@ -12,8 +16,8 @@ hop_headers: set[str] def load_entry_point(distribution: str, group: str, name: str) -> type[object]: ... def load_class( - uri: type[Logger | SyncWorker] | str, default: str = "gunicorn.workers.sync.SyncWorker", section: str = "gunicorn.workers" -) -> type[Logger | SyncWorker]: ... + uri: type[Logger | Worker] | str, default: str = "gunicorn.workers.sync.SyncWorker", section: str = "gunicorn.workers" +) -> type[Logger | Worker]: ... positionals: tuple[Literal[_ParameterKind.POSITIONAL_ONLY], Literal[_ParameterKind.POSITIONAL_OR_KEYWORD]] From 1c71b194d266066281ef65d1969d700175650031 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 25 Oct 2025 23:06:14 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks --- stubs/gunicorn/gunicorn/debug.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stubs/gunicorn/gunicorn/debug.pyi b/stubs/gunicorn/gunicorn/debug.pyi index c035d10dd323..57f96861b6be 100644 --- a/stubs/gunicorn/gunicorn/debug.pyi +++ b/stubs/gunicorn/gunicorn/debug.pyi @@ -13,7 +13,7 @@ class Spew: def __call__( self, frame: FrameType, - event: Literal["call"] | Literal["line"] | Literal["return"] | Literal["exception"] | Literal["opcode"], + event: Literal["call", "line", "return", "exception", "opcode"], arg: Any, # `arg` is not used inside the function, stub is set Any ) -> Self: ...