diff --git a/stubs/gunicorn/METADATA.toml b/stubs/gunicorn/METADATA.toml index f79a6d707ad7..c51c9ac86e68 100644 --- a/stubs/gunicorn/METADATA.toml +++ b/stubs/gunicorn/METADATA.toml @@ -1,4 +1,4 @@ -version = "23.0.*" +version = "24.1.0" upstream_repository = "https://github.com/benoitc/gunicorn" requires = ["types-gevent"] diff --git a/stubs/gunicorn/gunicorn/__init__.pyi b/stubs/gunicorn/gunicorn/__init__.pyi index dff515c23298..ddd769a92a3e 100644 --- a/stubs/gunicorn/gunicorn/__init__.pyi +++ b/stubs/gunicorn/gunicorn/__init__.pyi @@ -1,4 +1,6 @@ -version_info: tuple[int, int, int] -__version__: str -SERVER: str -SERVER_SOFTWARE: str +from typing import Final + +version_info: Final[tuple[int, int, int]] +__version__: Final[str] +SERVER: Final[str] +SERVER_SOFTWARE: Final[str] diff --git a/stubs/gunicorn/gunicorn/arbiter.pyi b/stubs/gunicorn/gunicorn/arbiter.pyi index 8d4f221aa857..169fe2136097 100644 --- a/stubs/gunicorn/gunicorn/arbiter.pyi +++ b/stubs/gunicorn/gunicorn/arbiter.pyi @@ -1,3 +1,4 @@ +from queue import SimpleQueue from types import FrameType from typing import ClassVar @@ -16,11 +17,11 @@ class Arbiter: START_CTX: ClassVar[dict[int | str, str | list[str]]] LISTENERS: ClassVar[list[BaseSocket]] WORKERS: ClassVar[dict[int, Worker]] - PIPE: ClassVar[list[int]] - SIG_QUEUE: ClassVar[list[int]] + WAKEUP_REQUEST: ClassVar[int] SIGNALS: ClassVar[list[int]] SIG_NAMES: ClassVar[dict[int, str]] log: GLogger | None + SIG_QUEUE: SimpleQueue[int] pidfile: Pidfile | None systemd: bool worker_age: int @@ -42,7 +43,9 @@ class Arbiter: def init_signals(self) -> None: ... def signal(self, sig: int, frame: FrameType | None) -> None: ... def run(self) -> None: ... - def handle_chld(self, sig: int, frame: FrameType | None) -> None: ... + def signal_chld(self, sig: int, frame: FrameType | None) -> None: ... + def handle_chld(self) -> None: ... + handle_cld = handle_chld def handle_hup(self) -> None: ... def handle_term(self) -> None: ... def handle_int(self) -> None: ... @@ -55,7 +58,7 @@ class Arbiter: def maybe_promote_master(self) -> None: ... def wakeup(self) -> None: ... def halt(self, reason: str | None = None, exit_status: int = 0) -> None: ... - def sleep(self) -> None: ... + def wait_for_signals(self, timeout: float | None = 1.0) -> list[int]: ... def stop(self, graceful: bool = True) -> None: ... def reexec(self) -> None: ... def reload(self) -> None: ... diff --git a/stubs/gunicorn/gunicorn/asgi/__init__.pyi b/stubs/gunicorn/gunicorn/asgi/__init__.pyi new file mode 100644 index 000000000000..7469610eac93 --- /dev/null +++ b/stubs/gunicorn/gunicorn/asgi/__init__.pyi @@ -0,0 +1,5 @@ +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"] diff --git a/stubs/gunicorn/gunicorn/asgi/lifespan.pyi b/stubs/gunicorn/gunicorn/asgi/lifespan.pyi new file mode 100644 index 000000000000..a55dca666674 --- /dev/null +++ b/stubs/gunicorn/gunicorn/asgi/lifespan.pyi @@ -0,0 +1,14 @@ +from _typeshed import Incomplete + +from gunicorn.glogging import Logger as GLogger + +from .._types import _ASGIAppType + +class LifespanManager: + app: _ASGIAppType + logger: GLogger + state: dict[Incomplete, Incomplete] + + def __init__(self, app: _ASGIAppType, logger: GLogger, state: dict[Incomplete, Incomplete] | None = None) -> None: ... + async def startup(self) -> None: ... + async def shutdown(self) -> None: ... diff --git a/stubs/gunicorn/gunicorn/asgi/message.pyi b/stubs/gunicorn/gunicorn/asgi/message.pyi new file mode 100644 index 000000000000..79cd0eecd889 --- /dev/null +++ b/stubs/gunicorn/gunicorn/asgi/message.pyi @@ -0,0 +1,50 @@ +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: ... + async def read_body(self, size: int = 8192) -> bytes: ... + async def drain_body(self) -> None: ... diff --git a/stubs/gunicorn/gunicorn/asgi/protocol.pyi b/stubs/gunicorn/gunicorn/asgi/protocol.pyi new file mode 100644 index 000000000000..7e757d334aa3 --- /dev/null +++ b/stubs/gunicorn/gunicorn/asgi/protocol.pyi @@ -0,0 +1,27 @@ +import asyncio +from collections.abc import Iterable + +from gunicorn.config import Config +from gunicorn.glogging import Logger as GLogger +from gunicorn.workers.gasgi import ASGIWorker + +from .._types import _ASGIAppType + +class ASGIResponseInfo: + status: str | int + sent: int + headers: list[tuple[str, str]] + + def __init__(self, status: str | int, headers: Iterable[tuple[str | bytes, str | bytes]], sent: int) -> None: ... + +class ASGIProtocol(asyncio.Protocol): + worker: ASGIWorker + cfg: Config + log: GLogger + app: _ASGIAppType + transport: asyncio.BaseTransport | None + reader: asyncio.StreamReader | None + writer: asyncio.BaseTransport | None + req_count: int + + def __init__(self, worker: ASGIWorker) -> None: ... diff --git a/stubs/gunicorn/gunicorn/asgi/unreader.pyi b/stubs/gunicorn/gunicorn/asgi/unreader.pyi new file mode 100644 index 000000000000..8bb53ea2f396 --- /dev/null +++ b/stubs/gunicorn/gunicorn/asgi/unreader.pyi @@ -0,0 +1,13 @@ +import asyncio +import io +from _typeshed import ReadableBuffer + +class AsyncUnreader: + reader: asyncio.StreamReader + buf: io.BytesIO + max_chunk: int + + def __init__(self, reader: asyncio.StreamReader, max_chunk: int = 8192) -> None: ... + async def read(self, size: int | None = None) -> bytes: ... + def unread(self, data: ReadableBuffer) -> None: ... + def has_buffered_data(self) -> bool: ... diff --git a/stubs/gunicorn/gunicorn/asgi/websocket.pyi b/stubs/gunicorn/gunicorn/asgi/websocket.pyi new file mode 100644 index 000000000000..f51aafc33da3 --- /dev/null +++ b/stubs/gunicorn/gunicorn/asgi/websocket.pyi @@ -0,0 +1,41 @@ +import asyncio +from typing import Final + +from gunicorn.glogging import Logger as GLogger + +from .._types import _ASGIAppType, _ScopeType + +OPCODE_CONTINUATION: Final = 0x0 +OPCODE_TEXT: Final = 0x1 +OPCODE_BINARY: Final = 0x2 +OPCODE_CLOSE: Final = 0x8 +OPCODE_PING: Final = 0x9 +OPCODE_PONG: Final = 0xA +CLOSE_NORMAL: Final = 1000 +CLOSE_GOING_AWAY: Final = 1001 +CLOSE_PROTOCOL_ERROR: Final = 1002 +CLOSE_UNSUPPORTED: Final = 1003 +CLOSE_NO_STATUS: Final = 1005 +CLOSE_ABNORMAL: Final = 1006 +CLOSE_INVALID_DATA: Final = 1007 +CLOSE_POLICY_VIOLATION: Final = 1008 +CLOSE_MESSAGE_TOO_BIG: Final = 1009 +CLOSE_MANDATORY_EXT: Final = 1010 +CLOSE_INTERNAL_ERROR: Final = 1011 +WS_GUID: Final = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + +class WebSocketProtocol: + transport: asyncio.Transport + reader: asyncio.StreamReader + scope: _ScopeType + app: _ASGIAppType + log: GLogger + accepted: bool + closed: bool + close_code: int | None + close_reason: str | None + + def __init__( + self, transport: asyncio.Transport, reader: asyncio.StreamReader, scope: _ScopeType, app: _ASGIAppType, log: GLogger + ) -> None: ... + async def run(self) -> None: ... diff --git a/stubs/gunicorn/gunicorn/config.pyi b/stubs/gunicorn/gunicorn/config.pyi index a6bd58c38b06..9875d85e088b 100644 --- a/stubs/gunicorn/gunicorn/config.pyi +++ b/stubs/gunicorn/gunicorn/config.pyi @@ -63,6 +63,9 @@ _ClassValidatorType: TypeAlias = Callable[[object | str | None], type[Any] | Non _UserGroupValidatorType: TypeAlias = Callable[[str | int | None], int] _AddressValidatorType: TypeAlias = Callable[[str | None], _AddressType | None] _CallableValidatorType: TypeAlias = Callable[[str | _HookType], _HookType] +_ProxyProtocolValidatorType: TypeAlias = Callable[[str | bool | None], str] +_ASGILoopValidatorType: TypeAlias = Callable[[str | None], str] +_ASGILifespanValidatorType: TypeAlias = Callable[[str | None], str] _ValidatorType: TypeAlias = ( # noqa: Y047 _BoolValidatorType @@ -74,6 +77,9 @@ _ValidatorType: TypeAlias = ( # noqa: Y047 | _UserGroupValidatorType | _AddressValidatorType | _CallableValidatorType + | _ProxyProtocolValidatorType + | _ASGILoopValidatorType + | _ASGILifespanValidatorType ) KNOWN_SETTINGS: list[Setting] @@ -138,7 +144,7 @@ class Setting(metaclass=SettingMeta): short: ClassVar[str | None] desc: ClassVar[str | None] nargs: ClassVar[int | str | None] - const: ClassVar[bool | None] + const: ClassVar[bool | str | None] order: ClassVar[int] def __init__(self) -> None: ... @@ -649,6 +655,7 @@ class SyslogTo(Setting): validator: ClassVar[_StringValidatorType] default: ClassVar[str] desc: ClassVar[str] + default_doc: ClassVar[str] class Syslog(Setting): name: ClassVar[str] @@ -713,6 +720,15 @@ class StatsdPrefix(Setting): validator: ClassVar[_StringValidatorType] desc: ClassVar[str] +class BacklogMetric(Setting): + name: ClassVar[str] + section: ClassVar[str] + cli: ClassVar[list[str]] + validator: ClassVar[_BoolValidatorType] + default: ClassVar[bool] + action: ClassVar[str] + desc: ClassVar[str] + class Procname(Setting): name: ClassVar[str] section: ClassVar[str] @@ -906,13 +922,17 @@ class NewSSLContext(Setting): def ssl_context(config: Config, default_ssl_context_factory: Callable[[], SSLContext]) -> SSLContext: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] +def validate_proxy_protocol(val: str | bool | None) -> str: ... + class ProxyProtocol(Setting): name: ClassVar[str] section: ClassVar[str] cli: ClassVar[list[str]] - validator: ClassVar[_BoolValidatorType] - default: ClassVar[bool] - action: ClassVar[str] + meta: ClassVar[str] + validator: ClassVar[_ProxyProtocolValidatorType] + default: ClassVar[str] + nargs: ClassVar[str] + const: ClassVar[str] desc: ClassVar[str] class ProxyAllowFrom(Setting): @@ -923,6 +943,23 @@ class ProxyAllowFrom(Setting): default: ClassVar[str] desc: ClassVar[str] +class Protocol(Setting): + name: ClassVar[str] + section: ClassVar[str] + cli: ClassVar[list[str]] + meta: ClassVar[str] + validator: ClassVar[_StringValidatorType] + default: ClassVar[str] + desc: ClassVar[str] + +class UWSGIAllowFrom(Setting): + name: ClassVar[str] + section: ClassVar[str] + cli: ClassVar[list[str]] + validator: ClassVar[_ListStringValidatorType] + default: ClassVar[str] + desc: ClassVar[str] + class KeyFile(Setting): name: ClassVar[str] section: ClassVar[str] @@ -1062,3 +1099,33 @@ class HeaderMap(Setting): validator: ClassVar[_StringValidatorType] default: ClassVar[str] desc: ClassVar[str] + +def validate_asgi_loop(val: str | None) -> str: ... +def validate_asgi_lifespan(val: str | None) -> str: ... + +class ASGILoop(Setting): + name: ClassVar[str] + section: ClassVar[str] + cli: ClassVar[list[str]] + meta: ClassVar[str] + validator: ClassVar[_ASGILoopValidatorType] + default: ClassVar[str] + desc: ClassVar[str] + +class ASGILifespan(Setting): + name: ClassVar[str] + section: ClassVar[str] + cli: ClassVar[list[str]] + meta: ClassVar[str] + validator: ClassVar[_ASGILifespanValidatorType] + default: ClassVar[str] + desc: ClassVar[str] + +class RootPath(Setting): + name: ClassVar[str] + section: ClassVar[str] + cli: ClassVar[list[str]] + meta: ClassVar[str] + validator: ClassVar[_StringValidatorType] + default: ClassVar[str] + desc: ClassVar[str] diff --git a/stubs/gunicorn/gunicorn/http/__init__.pyi b/stubs/gunicorn/gunicorn/http/__init__.pyi index 4fb87e5feebc..9b35cbbe56cc 100644 --- a/stubs/gunicorn/gunicorn/http/__init__.pyi +++ b/stubs/gunicorn/gunicorn/http/__init__.pyi @@ -1,4 +1,15 @@ +import socket +from collections.abc import Iterable + +from gunicorn.config import Config from gunicorn.http.message import Message as Message, Request as Request from gunicorn.http.parser import RequestParser as RequestParser +from gunicorn.uwsgi.parser import UWSGIParser + +from .._types import _AddressType + +def get_parser( + cfg: Config, source: socket.socket | Iterable[bytes], source_addr: _AddressType +) -> UWSGIParser | RequestParser: ... -__all__ = ["Message", "Request", "RequestParser"] +__all__ = ["Message", "Request", "RequestParser", "get_parser"] diff --git a/stubs/gunicorn/gunicorn/http/errors.pyi b/stubs/gunicorn/gunicorn/http/errors.pyi index 27c3e3e12db7..69cfd9ff8aef 100644 --- a/stubs/gunicorn/gunicorn/http/errors.pyi +++ b/stubs/gunicorn/gunicorn/http/errors.pyi @@ -80,6 +80,12 @@ class InvalidProxyLine(ParseException): def __init__(self, line: str) -> None: ... +class InvalidProxyHeader(ParseException): + msg: str + code: int + + def __init__(self, msg: str) -> None: ... + class ForbiddenProxyRequest(ParseException): host: str code: int diff --git a/stubs/gunicorn/gunicorn/http/message.pyi b/stubs/gunicorn/gunicorn/http/message.pyi index e72e5e71efad..dfa351423ef0 100644 --- a/stubs/gunicorn/gunicorn/http/message.pyi +++ b/stubs/gunicorn/gunicorn/http/message.pyi @@ -1,5 +1,7 @@ import io import re +from enum import IntEnum +from typing import Final from gunicorn.config import Config from gunicorn.http.body import Body @@ -7,14 +9,31 @@ from gunicorn.http.unreader import Unreader from .._types import _AddressType -MAX_REQUEST_LINE: int -MAX_HEADERS: int -DEFAULT_MAX_HEADERFIELD_SIZE: int -RFC9110_5_6_2_TOKEN_SPECIALS: str -TOKEN_RE: re.Pattern[str] -METHOD_BADCHAR_RE: re.Pattern[str] -VERSION_RE: re.Pattern[str] -RFC9110_5_5_INVALID_AND_DANGEROUS: re.Pattern[str] +PP_V2_SIGNATURE: Final = b"\x0d\x0a\x0d\x0a\x00\x0d\x0a\x51\x55\x49\x54\x0a" + +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 + +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 Message: cfg: Config @@ -46,14 +65,14 @@ class Request(Message): fragment: str | None limit_request_line: int req_number: int - proxy_protocol_info: dict[str, str | int] | None + proxy_protocol_info: dict[str, str | int | None] | None # TODO: Use TypedDict def __init__(self, cfg: Config, unreader: Unreader, peer_addr: _AddressType, req_number: int = 1) -> None: ... def get_data(self, unreader: Unreader, buf: io.BytesIO, stop: bool = False) -> None: ... def parse(self, unreader: Unreader) -> bytes: ... - def read_line(self, unreader: Unreader, buf: io.BytesIO, limit: int = 0) -> tuple[bytes, bytes]: ... - def proxy_protocol(self, line: str) -> bool: ... + def read_into(self, unreader: Unreader, buf: bytearray, stop: bool | None = False) -> None: ... + def read_line(self, unreader: Unreader, buf: bytearray, limit: int = 0) -> tuple[bytes, bytearray]: ... + def read_bytes(self, unreader: Unreader, buf: bytearray, count: int) -> tuple[bytes, bytearray]: ... def proxy_protocol_access_check(self) -> None: ... - def parse_proxy_protocol(self, line: str) -> None: ... def parse_request_line(self, line_bytes: bytes) -> None: ... def set_body_reader(self) -> None: ... diff --git a/stubs/gunicorn/gunicorn/http/parser.pyi b/stubs/gunicorn/gunicorn/http/parser.pyi index 644581644b70..7d0006b3ddbc 100644 --- a/stubs/gunicorn/gunicorn/http/parser.pyi +++ b/stubs/gunicorn/gunicorn/http/parser.pyi @@ -9,6 +9,7 @@ from gunicorn.http.unreader import Unreader from .._types import _AddressType class Parser: + # TODO: Use Protocol instead of Request class mesg_class: ClassVar[type[Request] | None] cfg: Config unreader: Unreader @@ -18,6 +19,7 @@ class Parser: def __init__(self, cfg: Config, source: socket.socket | Iterable[bytes], source_addr: _AddressType) -> None: ... def __iter__(self) -> Iterator[Request]: ... + def finish_body(self) -> None: ... def __next__(self) -> Request: ... next: Callable[[Parser], Request] diff --git a/stubs/gunicorn/gunicorn/http/wsgi.pyi b/stubs/gunicorn/gunicorn/http/wsgi.pyi index be6037952d5e..5dec598ca7d8 100644 --- a/stubs/gunicorn/gunicorn/http/wsgi.pyi +++ b/stubs/gunicorn/gunicorn/http/wsgi.pyi @@ -4,15 +4,15 @@ import re import socket from _typeshed import ReadableBuffer from collections.abc import Callable -from typing import Any +from typing import Any, Final from gunicorn.config import Config from gunicorn.http import Request from .._types import _AddressType, _EnvironType, _HeadersType, _StatusType -BLKSIZE: int -HEADER_VALUE_RE: re.Pattern[str] +BLKSIZE: Final = 0x3FFFFFFF +HEADER_VALUE_RE: Final[re.Pattern[str]] log: logging.Logger class FileWrapper: diff --git a/stubs/gunicorn/gunicorn/instrument/statsd.pyi b/stubs/gunicorn/gunicorn/instrument/statsd.pyi index 5c47d55878c8..d08e86de095d 100644 --- a/stubs/gunicorn/gunicorn/instrument/statsd.pyi +++ b/stubs/gunicorn/gunicorn/instrument/statsd.pyi @@ -2,6 +2,7 @@ import logging import socket from collections.abc import Mapping from datetime import timedelta +from typing import Final from gunicorn.config import Config from gunicorn.glogging import Logger @@ -11,12 +12,13 @@ from gunicorn.http.wsgi import Response from .._types import _EnvironType from ..glogging import _LogLevelType -METRIC_VAR: str -VALUE_VAR: str -MTYPE_VAR: str -GAUGE_TYPE: str -COUNTER_TYPE: str -HISTOGRAM_TYPE: str +METRIC_VAR: Final = "metric" +VALUE_VAR: Final = "value" +MTYPE_VAR: Final = "mtype" +GAUGE_TYPE: Final = "gauge" +COUNTER_TYPE: Final = "counter" +HISTOGRAM_TYPE: Final = "histogram" +TIMER_TYPE: Final = "timer" class Statsd(Logger): prefix: str @@ -93,4 +95,5 @@ class Statsd(Logger): def gauge(self, name: str, value: float) -> None: ... def increment(self, name: str, value: int, sampling_rate: float = 1.0) -> None: ... def decrement(self, name: str, value: int, sampling_rate: float = 1.0) -> None: ... + def timer(self, name: str, value: float) -> None: ... def histogram(self, name: str, value: float) -> None: ... diff --git a/stubs/gunicorn/gunicorn/reloader.pyi b/stubs/gunicorn/gunicorn/reloader.pyi index 6d12d8fd3a8a..7d729d281949 100644 --- a/stubs/gunicorn/gunicorn/reloader.pyi +++ b/stubs/gunicorn/gunicorn/reloader.pyi @@ -2,12 +2,12 @@ import sys import threading from collections.abc import Callable, Iterable from re import Pattern -from typing import NoReturn, TypedDict, type_check_only +from typing import Final, NoReturn, TypedDict, type_check_only from typing_extensions import TypeAlias -COMPILED_EXT_RE: Pattern[str] +COMPILED_EXT_RE: Final[Pattern[str]] -class Reloader(threading.Thread): +class ReloaderBase(threading.Thread): daemon: bool def __init__( @@ -15,18 +15,21 @@ class Reloader(threading.Thread): ) -> None: ... def add_extra_file(self, filename: str) -> None: ... def get_files(self) -> list[str]: ... + +class Reloader(ReloaderBase): def run(self) -> None: ... has_inotify: bool if sys.platform == "linux": - class InotifyReloader(threading.Thread): + class InotifyReloader(ReloaderBase): event_mask: int daemon: bool def __init__(self, extra_files: Iterable[str] | None = None, callback: Callable[[str], None] | None = None) -> None: ... def add_extra_file(self, filename: str) -> None: ... def get_dirs(self) -> set[str]: ... + def refresh_dirs(self) -> None: ... def run(self) -> None: ... else: diff --git a/stubs/gunicorn/gunicorn/sock.pyi b/stubs/gunicorn/gunicorn/sock.pyi index 0c19078abe8a..5afa7ba185c6 100644 --- a/stubs/gunicorn/gunicorn/sock.pyi +++ b/stubs/gunicorn/gunicorn/sock.pyi @@ -2,12 +2,14 @@ import socket import sys from collections.abc import Iterable from ssl import SSLContext, SSLSocket -from typing import Any, ClassVar, Literal, SupportsIndex +from typing import Any, ClassVar, Final, Literal, SupportsIndex from gunicorn.glogging import Logger as GLogger from .config import Config +PLATFORM: Final[str] + class BaseSocket: sock: socket.socket @@ -16,11 +18,13 @@ class BaseSocket: def set_options(self, sock: socket.socket, bound: bool = False) -> socket.socket: ... def bind(self, sock: socket.socket) -> None: ... def close(self) -> None: ... + def get_backlog(self) -> int: ... class TCPSocket(BaseSocket): FAMILY: ClassVar[Literal[socket.AddressFamily.AF_INET, socket.AddressFamily.AF_INET6]] def set_options(self, sock: socket.socket, bound: bool = False) -> socket.socket: ... + def get_backlog(self) -> int: ... class TCP6Socket(TCPSocket): FAMILY: ClassVar[Literal[socket.AddressFamily.AF_INET6]] diff --git a/stubs/gunicorn/gunicorn/systemd.pyi b/stubs/gunicorn/gunicorn/systemd.pyi index 1645f0e68052..a7facceeec92 100644 --- a/stubs/gunicorn/gunicorn/systemd.pyi +++ b/stubs/gunicorn/gunicorn/systemd.pyi @@ -1,6 +1,8 @@ +from typing import Final + from gunicorn.glogging import Logger as GLogger -SD_LISTEN_FDS_START: int +SD_LISTEN_FDS_START: Final[int] def listen_fds(unset_environment: bool = True) -> int: ... def sd_notify(state: str, logger: GLogger, unset_environment: bool = False) -> None: ... diff --git a/stubs/gunicorn/gunicorn/uwsgi/__init__.pyi b/stubs/gunicorn/gunicorn/uwsgi/__init__.pyi new file mode 100644 index 000000000000..47193b1757e6 --- /dev/null +++ b/stubs/gunicorn/gunicorn/uwsgi/__init__.pyi @@ -0,0 +1,17 @@ +from gunicorn.uwsgi.errors import ( + ForbiddenUWSGIRequest as ForbiddenUWSGIRequest, + InvalidUWSGIHeader as InvalidUWSGIHeader, + UnsupportedModifier as UnsupportedModifier, + UWSGIParseException as UWSGIParseException, +) +from gunicorn.uwsgi.message import UWSGIRequest as UWSGIRequest +from gunicorn.uwsgi.parser import UWSGIParser as UWSGIParser + +__all__ = [ + "UWSGIRequest", + "UWSGIParser", + "UWSGIParseException", + "InvalidUWSGIHeader", + "UnsupportedModifier", + "ForbiddenUWSGIRequest", +] diff --git a/stubs/gunicorn/gunicorn/uwsgi/errors.pyi b/stubs/gunicorn/gunicorn/uwsgi/errors.pyi new file mode 100644 index 000000000000..265536815654 --- /dev/null +++ b/stubs/gunicorn/gunicorn/uwsgi/errors.pyi @@ -0,0 +1,19 @@ +class UWSGIParseException(Exception): ... + +class InvalidUWSGIHeader(UWSGIParseException): + msg: str + code: int + + def __init__(self, msg: str = "") -> None: ... + +class UnsupportedModifier(UWSGIParseException): + modifier: int + code: int + + def __init__(self, modifier: int) -> None: ... + +class ForbiddenUWSGIRequest(UWSGIParseException): + host: str + code: int + + def __init__(self, host: str) -> None: ... diff --git a/stubs/gunicorn/gunicorn/uwsgi/message.pyi b/stubs/gunicorn/gunicorn/uwsgi/message.pyi new file mode 100644 index 000000000000..fbd9f318ebc7 --- /dev/null +++ b/stubs/gunicorn/gunicorn/uwsgi/message.pyi @@ -0,0 +1,37 @@ +from typing import Final, Literal + +from gunicorn.config import Config +from gunicorn.http.body import Body +from gunicorn.http.unreader import Unreader + +from .._types import _AddressType + +MAX_UWSGI_VARS: Final = 1000 + +class UWSGIRequest: + cfg: Config + unreader: Unreader + peer_addr: _AddressType + remote_addr: _AddressType + req_number: int + method: str | None + uri: str | None + path: str | None + query: str | None + fragment: str | None + version: tuple[int, int] + headers: list[tuple[str, str]] + trailers: list[tuple[str, str]] + body: Body | None + scheme: Literal["https", "http"] + must_close: bool + uwsgi_vars: dict[str, str] + modifier1: int + modifier2: int + proxy_protocol_info: dict[str, str | int | None] | None # TODO: Use TypedDict + + def __init__(self, cfg: Config, unreader: Unreader, peer_addr: _AddressType, req_number: int = 1) -> None: ... + def force_close(self) -> None: ... + def parse(self, unreader: Unreader) -> bytes: ... + def set_body_reader(self) -> None: ... + def should_close(self) -> bool: ... diff --git a/stubs/gunicorn/gunicorn/uwsgi/parser.pyi b/stubs/gunicorn/gunicorn/uwsgi/parser.pyi new file mode 100644 index 000000000000..488b318b2299 --- /dev/null +++ b/stubs/gunicorn/gunicorn/uwsgi/parser.pyi @@ -0,0 +1,7 @@ +from typing import ClassVar + +from gunicorn.http.parser import Parser +from gunicorn.uwsgi.message import UWSGIRequest + +class UWSGIParser(Parser): + mesg_class: ClassVar[type[UWSGIRequest]] # type: ignore[assignment] diff --git a/stubs/gunicorn/gunicorn/workers/__init__.pyi b/stubs/gunicorn/gunicorn/workers/__init__.pyi index b1e6ab0b1350..0223bc5118db 100644 --- a/stubs/gunicorn/gunicorn/workers/__init__.pyi +++ b/stubs/gunicorn/gunicorn/workers/__init__.pyi @@ -9,5 +9,6 @@ class _SupportedWorkers(TypedDict): gevent_pywsgi: str tornado: str gthread: str + asgi: str SUPPORTED_WORKERS: _SupportedWorkers diff --git a/stubs/gunicorn/gunicorn/workers/gasgi.pyi b/stubs/gunicorn/gunicorn/workers/gasgi.pyi new file mode 100644 index 000000000000..368556ffab0c --- /dev/null +++ b/stubs/gunicorn/gunicorn/workers/gasgi.pyi @@ -0,0 +1,32 @@ +from _typeshed import Incomplete +from asyncio.base_events import Server +from asyncio.events import AbstractEventLoop +from typing import NoReturn + +from gunicorn.asgi.lifespan import LifespanManager +from gunicorn.config import Config +from gunicorn.glogging import Logger as GLogger +from gunicorn.workers import base + +from .._types import _ASGIAppType + +class ASGIWorker(base.Worker): + worker_connections: int + loop: AbstractEventLoop | None + servers: list[Server] + nr_conns: int + lifespan: LifespanManager | None + state: dict[Incomplete, Incomplete] + asgi: _ASGIAppType + + @classmethod + def check_config(cls, cfg: Config, log: GLogger) -> None: ... + def init_process(self) -> None: ... + def load_wsgi(self) -> None: ... + def init_signals(self) -> None: ... + def handle_quit_signal(self) -> None: ... + def handle_exit_signal(self) -> None: ... + def handle_usr1_signal(self) -> None: ... + def handle_winch_signal(self) -> None: ... + def handle_abort_signal(self) -> NoReturn: ... + def run(self) -> None: ... diff --git a/stubs/gunicorn/gunicorn/workers/ggevent.pyi b/stubs/gunicorn/gunicorn/workers/ggevent.pyi index 2fa479231291..4f16eaea8a44 100644 --- a/stubs/gunicorn/gunicorn/workers/ggevent.pyi +++ b/stubs/gunicorn/gunicorn/workers/ggevent.pyi @@ -1,5 +1,5 @@ from types import FrameType -from typing import Any, ClassVar +from typing import Any, ClassVar, Final from gevent import pywsgi from gevent.pywsgi import WSGIHandler @@ -10,7 +10,7 @@ from gunicorn.workers.base_async import AsyncWorker from .._types import _AddressType -VERSION: str +VERSION: Final[str] class GeventWorker(AsyncWorker): server_class: ClassVar[type[StreamServer] | None] diff --git a/stubs/gunicorn/gunicorn/workers/gthread.pyi b/stubs/gunicorn/gunicorn/workers/gthread.pyi index c3057c7e26b5..5629df3c28c8 100644 --- a/stubs/gunicorn/gunicorn/workers/gthread.pyi +++ b/stubs/gunicorn/gunicorn/workers/gthread.pyi @@ -1,5 +1,7 @@ import socket +from _typeshed import Unused from collections import deque +from collections.abc import Callable from concurrent.futures import Future, ThreadPoolExecutor from selectors import DefaultSelector from types import FrameType @@ -25,12 +27,24 @@ class TConn: def set_timeout(self) -> None: ... def close(self) -> None: ... +class PollableMethodQueue: + def __init__(self) -> None: ... + def init(self) -> None: ... + def close(self) -> None: ... + def fileno(self) -> int | None: ... + # Actually, `*args` are expected to match the parameter types of `callback`. + # Ideally this would be typed using ParamSpec as: + # def defer(self, callback: Callable[_P, object], *args: _P.args) -> None + def defer(self, callback: Callable[..., object], *args: object) -> None: ... + def run_callbacks(self, _fileobj: Unused, max_callbacks: int = 50) -> None: ... + class ThreadWorker(base.Worker): worker_connections: int max_keepalived: int tpool: ThreadPoolExecutor poller: DefaultSelector - futures: deque[Future[tuple[bool, TConn]]] + method_queue: PollableMethodQueue + keepalived_conns: deque[TConn] nr_conns: int alive: bool @@ -38,13 +52,16 @@ class ThreadWorker(base.Worker): def check_config(cls, cfg: Config, log: GLogger) -> None: ... def init_process(self) -> None: ... def get_thread_pool(self) -> ThreadPoolExecutor: ... + def handle_exit(self, sig: int, frame: FrameType | None) -> None: ... def handle_quit(self, sig: int, frame: FrameType | None) -> None: ... + def set_accept_enabled(self, enabled: bool | None) -> None: ... def enqueue_req(self, conn: TConn) -> None: ... - def accept(self, server: _AddressType, listener: socket.socket) -> None: ... + def accept(self, listener: socket.socket) -> None: ... def on_client_socket_readable(self, conn: TConn, client: socket.socket) -> None: ... def murder_keepalived(self) -> None: ... def is_parent_alive(self) -> bool: ... + def wait_for_and_dispatch_events(self, timeout: float | None) -> None: ... def run(self) -> None: ... - def finish_request(self, fs: Future[tuple[bool, TConn]]) -> None: ... - def handle(self, conn: TConn) -> tuple[bool, TConn]: ... + def finish_request(self, conn: TConn, fs: Future[bool]) -> None: ... + def handle(self, conn: TConn) -> bool: ... def handle_request(self, req: RequestParser, conn: TConn) -> bool: ... diff --git a/stubs/gunicorn/gunicorn/workers/gtornado.pyi b/stubs/gunicorn/gunicorn/workers/gtornado.pyi index 83874241e5f4..cac5ed08e525 100644 --- a/stubs/gunicorn/gunicorn/workers/gtornado.pyi +++ b/stubs/gunicorn/gunicorn/workers/gtornado.pyi @@ -6,14 +6,14 @@ from gunicorn.workers.base import Worker IOLoop: TypeAlias = Any # tornado IOLoop class PeriodicCallback: TypeAlias = Any # tornado PeriodicCallback class - -TORNADO5: bool +_HTTPServer: TypeAlias = Any # tornado httpserver.HTTPServer class class TornadoWorker(Worker): alive: bool server_alive: bool ioloop: IOLoop callbacks: list[PeriodicCallback] + server: _HTTPServer @classmethod def setup(cls) -> None: ... diff --git a/stubs/gunicorn/gunicorn/workers/workertmp.pyi b/stubs/gunicorn/gunicorn/workers/workertmp.pyi index 0ee89ce3686d..4bfc3c6182f9 100644 --- a/stubs/gunicorn/gunicorn/workers/workertmp.pyi +++ b/stubs/gunicorn/gunicorn/workers/workertmp.pyi @@ -1,7 +1,9 @@ +from typing import Final + from gunicorn.config import Config -PLATFORM: str -IS_CYGWIN: bool +PLATFORM: Final[str] +IS_CYGWIN: Final[bool] class WorkerTmp: def __init__(self, cfg: Config) -> None: ...