From 7f21c4ca6e65ec7cb91aa689a011576ab6535a16 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 20 Jul 2026 14:25:42 +0100 Subject: [PATCH 01/14] Add stubs for HTTPretty --- .../@tests/test_cases/check_usage.py | 17 ++ stubs/httpretty/METADATA.toml | 6 + stubs/httpretty/httpretty/__init__.pyi | 39 ++++ stubs/httpretty/httpretty/compat.pyi | 7 + stubs/httpretty/httpretty/core.pyi | 203 ++++++++++++++++++ stubs/httpretty/httpretty/errors.pyi | 4 + stubs/httpretty/httpretty/http.pyi | 21 ++ stubs/httpretty/httpretty/utils.pyi | 2 + stubs/httpretty/httpretty/version.pyi | 1 + 9 files changed, 300 insertions(+) create mode 100644 stubs/httpretty/@tests/test_cases/check_usage.py create mode 100644 stubs/httpretty/METADATA.toml create mode 100644 stubs/httpretty/httpretty/__init__.pyi create mode 100644 stubs/httpretty/httpretty/compat.pyi create mode 100644 stubs/httpretty/httpretty/core.pyi create mode 100644 stubs/httpretty/httpretty/errors.pyi create mode 100644 stubs/httpretty/httpretty/http.pyi create mode 100644 stubs/httpretty/httpretty/utils.pyi create mode 100644 stubs/httpretty/httpretty/version.pyi diff --git a/stubs/httpretty/@tests/test_cases/check_usage.py b/stubs/httpretty/@tests/test_cases/check_usage.py new file mode 100644 index 000000000000..664d8dfbf7fa --- /dev/null +++ b/stubs/httpretty/@tests/test_cases/check_usage.py @@ -0,0 +1,17 @@ +from typing_extensions import assert_type + +import httpretty + + +@httpretty.activate(allow_net_connect=False) +def decorated() -> int: + httpretty.register_uri(httpretty.GET, "https://example.com/", body="ok", status=200, content_type="text/plain") + assert_type(httpretty.last_request(), httpretty.HTTPrettyRequest | httpretty.HTTPrettyRequestEmpty) + assert_type(httpretty.latest_requests(), list[httpretty.HTTPrettyRequest]) + assert_type(httpretty.has_request(), bool) + return 1 + + +with httpretty.enabled(allow_net_connect=False): + response = httpretty.Response("ok", status=201) + assert_type(response, httpretty.Entry) diff --git a/stubs/httpretty/METADATA.toml b/stubs/httpretty/METADATA.toml new file mode 100644 index 000000000000..84ef9a4c2452 --- /dev/null +++ b/stubs/httpretty/METADATA.toml @@ -0,0 +1,6 @@ +version = "1.1.4" +upstream-repository = "https://github.com/gabrielfalcao/HTTPretty" +partial-stub = true + +[tool.stubtest] +ignore-missing-stub = true diff --git a/stubs/httpretty/httpretty/__init__.pyi b/stubs/httpretty/httpretty/__init__.pyi new file mode 100644 index 000000000000..920463afee4b --- /dev/null +++ b/stubs/httpretty/httpretty/__init__.pyi @@ -0,0 +1,39 @@ +from typing import Literal + +from .core import ( + EmptyRequestHeaders as EmptyRequestHeaders, + Entry as Entry, + HTTPrettyRequest as HTTPrettyRequest, + HTTPrettyRequestEmpty as HTTPrettyRequestEmpty, + URIInfo as URIInfo, + URIMatcher as URIMatcher, + get_default_thread_timeout as get_default_thread_timeout, + httprettified as httprettified, + httprettized as httprettized, + httpretty as httpretty, + set_default_thread_timeout as set_default_thread_timeout, +) +from .errors import HTTPrettyError as HTTPrettyError, UnmockedError as UnmockedError + +__version__: str +HTTPretty = httpretty +activate = httprettified +enabled = httprettized +enable = httpretty.enable +register_uri = httpretty.register_uri +disable = httpretty.disable +is_enabled = httpretty.is_enabled +reset = httpretty.reset +Response = httpretty.Response +GET: Literal["GET"] +PUT: Literal["PUT"] +POST: Literal["POST"] +DELETE: Literal["DELETE"] +HEAD: Literal["HEAD"] +PATCH: Literal["PATCH"] +OPTIONS: Literal["OPTIONS"] +CONNECT: Literal["CONNECT"] + +def last_request() -> HTTPrettyRequest | HTTPrettyRequestEmpty: ... +def latest_requests() -> list[HTTPrettyRequest]: ... +def has_request() -> bool: ... diff --git a/stubs/httpretty/httpretty/compat.pyi b/stubs/httpretty/httpretty/compat.pyi new file mode 100644 index 000000000000..aa9ef01dc4b4 --- /dev/null +++ b/stubs/httpretty/httpretty/compat.pyi @@ -0,0 +1,7 @@ +from typing import TypeVar + +_T = TypeVar("_T") + +class BaseClass: ... + +def encode_obj(in_obj: _T) -> _T: ... diff --git a/stubs/httpretty/httpretty/core.pyi b/stubs/httpretty/httpretty/core.pyi new file mode 100644 index 000000000000..d421bba73e79 --- /dev/null +++ b/stubs/httpretty/httpretty/core.pyi @@ -0,0 +1,203 @@ +import re +from collections.abc import Callable, Iterable, Mapping +from contextlib import AbstractContextManager +from http.client import HTTPMessage +from types import TracebackType +from typing import Any, Literal, TypeAlias, overload +from typing_extensions import ParamSpec + +from .http import HttpBaseClass + +_P = ParamSpec("_P") +_HTTPMethod: TypeAlias = Literal["GET", "PUT", "POST", "DELETE", "HEAD", "PATCH", "OPTIONS", "CONNECT"] +_URI: TypeAlias = str | re.Pattern[str] +_HeaderValue: TypeAlias = str | int | bool | None +_Headers: TypeAlias = Mapping[str, _HeaderValue] +_Body: TypeAlias = str | bytes +_ResponseBody: TypeAlias = _Body | Callable[[HTTPrettyRequest, str, _Headers], tuple[int, _Headers, _Body]] + +def set_default_thread_timeout(timeout: float) -> None: ... +def get_default_thread_timeout() -> float: ... + +class HTTPrettyRequest(HttpBaseClass): + headers: HTTPMessage + raw_headers: str + path: str + querystring: dict[str, list[str]] + parsed_body: Any + created_at: float + def __init__( + self, headers: str | bytes, body: _Body = "", sock: object | None = None, path_encoding: str = "iso-8859-1" + ) -> None: ... + @property + def method(self) -> str: ... + @property + def protocol(self) -> str: ... + + @property + def body(self) -> str: ... + @body.setter + def body(self, value: _Body) -> None: ... + + @property + def url(self) -> str: ... + @property + def host(self) -> str: ... + def parse_querystring(self, qs: str) -> dict[str, list[str]]: ... + def parse_request_body(self, body: str) -> Any: ... + +class EmptyRequestHeaders(dict[str, str]): ... + +class HTTPrettyRequestEmpty: + method: str | None + url: str | None + body: str + headers: EmptyRequestHeaders + +class Entry(HttpBaseClass): + method: _HTTPMethod + uri: str + request: HTTPrettyRequest + body: _Body + status: int + streaming: bool + adding_headers: dict[str, str] + forcing_headers: dict[str, str] + def __init__( + self, + method: str, + uri: str, + body: _ResponseBody, + adding_headers: _Headers | None = None, + forcing_headers: _Headers | None = None, + status: int = 200, + streaming: bool = False, + **headers: str, + ) -> None: ... + def validate(self) -> None: ... + def normalize_headers(self, headers: _Headers) -> dict[str, str]: ... + def fill_filekind(self, fk: Any) -> None: ... + +class URIInfo(HttpBaseClass): + default_str_attrs: tuple[str, ...] + username: str + password: str + hostname: str + port: int + path: str + query: str + scheme: str + fragment: str + last_request: HTTPrettyRequest | None + def __init__( + self, + username: str = "", + password: str = "", + hostname: str = "", + port: int = 80, + path: str = "/", + query: str = "", + fragment: str = "", + scheme: str = "", + last_request: HTTPrettyRequest | None = None, + ) -> None: ... + def to_str(self, attrs: Iterable[str]) -> str: ... + def str_with_query(self) -> str: ... + def full_url(self, use_querystring: bool = True) -> str: ... + def get_full_domain(self) -> str: ... + @classmethod + def from_uri(cls, uri: str, entry: Entry) -> URIInfo: ... + +class URIMatcher: + regex: re.Pattern[str] | None + info: URIInfo | None + entries: list[Entry] + priority: int + uri: _URI + def __init__(self, uri: _URI, entries: Iterable[Entry], match_querystring: bool = False, priority: int = 0) -> None: ... + def matches(self, info: URIInfo) -> bool: ... + def get_next_entry(self, method: _HTTPMethod, info: URIInfo, request: HTTPrettyRequest) -> Entry: ... + +class httpretty(HttpBaseClass): + GET: Literal["GET"] + PUT: Literal["PUT"] + POST: Literal["POST"] + DELETE: Literal["DELETE"] + HEAD: Literal["HEAD"] + PATCH: Literal["PATCH"] + OPTIONS: Literal["OPTIONS"] + CONNECT: Literal["CONNECT"] + METHODS: tuple[_HTTPMethod, ...] + latest_requests: list[HTTPrettyRequest] + last_request: HTTPrettyRequest | HTTPrettyRequestEmpty + allow_net_connect: bool + @classmethod + def match_uriinfo(cls, info: URIInfo) -> tuple[Entry | None, list[str]]: ... + @classmethod + def match_https_hostname(cls, hostname: str) -> bool: ... + @classmethod + def match_http_address(cls, hostname: str, port: int) -> bool: ... + @classmethod + def record( + cls, + filename: str, + indentation: int = 4, + encoding: str = "utf-8", + verbose: bool = False, + allow_net_connect: bool = True, + pool_manager_params: Mapping[str, Any] | None = None, + ) -> AbstractContextManager[None]: ... + @classmethod + def playback(cls, filename: str, allow_net_connect: bool = True, verbose: bool = False) -> AbstractContextManager[None]: ... + @classmethod + def reset(cls) -> None: ... + @classmethod + def historify_request(cls, headers: str | bytes, body: _Body = "", sock: object | None = None) -> HTTPrettyRequest: ... + @classmethod + def register_uri( + cls, + method: str, + uri: _URI, + body: _ResponseBody = '{"message": "HTTPretty :)"}', + adding_headers: _Headers | None = None, + forcing_headers: _Headers | None = None, + status: int = 200, + responses: Iterable[Entry] | None = None, + match_querystring: bool = False, + priority: int = 0, + **headers: str, + ) -> None: ... + @classmethod + def Response( + cls, + body: _ResponseBody, + method: _HTTPMethod | None = None, + uri: str | None = None, + adding_headers: _Headers | None = None, + forcing_headers: _Headers | None = None, + status: int = 200, + streaming: bool = False, + **headers: str, + ) -> Entry: ... + @classmethod + def disable(cls) -> None: ... + @classmethod + def is_enabled(cls) -> bool: ... + @classmethod + def enable(cls, allow_net_connect: bool = True, verbose: bool = False) -> None: ... + +class httprettized: + allow_net_connect: bool + verbose: bool + def __init__(self, allow_net_connect: bool = True, verbose: bool = False) -> None: ... + def __enter__(self) -> None: ... + def __exit__( + self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None + ) -> None: ... + +@overload +def httprettified(test: Callable[_P, Any]) -> Callable[_P, Any]: ... +@overload +def httprettified( + test: None = None, allow_net_connect: bool = True, verbose: bool = False +) -> Callable[[Callable[_P, Any]], Callable[_P, Any]]: ... diff --git a/stubs/httpretty/httpretty/errors.pyi b/stubs/httpretty/httpretty/errors.pyi new file mode 100644 index 000000000000..4fb6e583a119 --- /dev/null +++ b/stubs/httpretty/httpretty/errors.pyi @@ -0,0 +1,4 @@ +class HTTPrettyError(Exception): ... + +class UnmockedError(HTTPrettyError): + def __init__(self, message: str = ..., request: object | None = None, address: object | None = None) -> None: ... diff --git a/stubs/httpretty/httpretty/http.pyi b/stubs/httpretty/httpretty/http.pyi new file mode 100644 index 000000000000..660b3545fa53 --- /dev/null +++ b/stubs/httpretty/httpretty/http.pyi @@ -0,0 +1,21 @@ +from collections.abc import Sequence +from typing import Literal +from typing_extensions import TypeVar + +_T = TypeVar("_T", str, bytes) + +STATUSES: dict[int, str] + +class HttpBaseClass: + GET: Literal["GET"] + PUT: Literal["PUT"] + POST: Literal["POST"] + DELETE: Literal["DELETE"] + HEAD: Literal["HEAD"] + PATCH: Literal["PATCH"] + OPTIONS: Literal["OPTIONS"] + CONNECT: Literal["CONNECT"] + METHODS: tuple[Literal["GET", "PUT", "POST", "DELETE", "HEAD", "PATCH", "OPTIONS", "CONNECT"], ...] + +def parse_requestline(s: str) -> tuple[str, str, str]: ... +def last_requestline(sent_data: Sequence[_T]) -> _T | None: ... diff --git a/stubs/httpretty/httpretty/utils.pyi b/stubs/httpretty/httpretty/utils.pyi new file mode 100644 index 000000000000..b38062b2ea81 --- /dev/null +++ b/stubs/httpretty/httpretty/utils.pyi @@ -0,0 +1,2 @@ +def utf8(s: str | bytes) -> bytes: ... +def decode_utf8(s: str | bytes) -> str: ... diff --git a/stubs/httpretty/httpretty/version.pyi b/stubs/httpretty/httpretty/version.pyi new file mode 100644 index 000000000000..c2ee2cab489b --- /dev/null +++ b/stubs/httpretty/httpretty/version.pyi @@ -0,0 +1 @@ +version: str From 98a97ec96d29f7f115cacf2feae8f99cfbf30441 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 20 Jul 2026 20:17:19 +0100 Subject: [PATCH 02/14] Update stubs/httpretty/httpretty/__init__.pyi\ Co-authored-by: Semyon Moroz --- stubs/httpretty/httpretty/__init__.pyi | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/stubs/httpretty/httpretty/__init__.pyi b/stubs/httpretty/httpretty/__init__.pyi index 920463afee4b..10c32a092904 100644 --- a/stubs/httpretty/httpretty/__init__.pyi +++ b/stubs/httpretty/httpretty/__init__.pyi @@ -25,14 +25,14 @@ disable = httpretty.disable is_enabled = httpretty.is_enabled reset = httpretty.reset Response = httpretty.Response -GET: Literal["GET"] -PUT: Literal["PUT"] -POST: Literal["POST"] -DELETE: Literal["DELETE"] -HEAD: Literal["HEAD"] -PATCH: Literal["PATCH"] -OPTIONS: Literal["OPTIONS"] -CONNECT: Literal["CONNECT"] +GET: Final = "GET" +PUT: Final = "PUT" +POST: Final = "POST" +DELETE: Final = "DELETE" +HEAD: Final = "HEAD" +PATCH: Final = "PATCH" +OPTIONS: Final = "OPTIONS" +CONNECT: Final = "CONNECT" def last_request() -> HTTPrettyRequest | HTTPrettyRequestEmpty: ... def latest_requests() -> list[HTTPrettyRequest]: ... From 1f0d5e07f90a431ef7bb9f7770ac593e5f2b04a2 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 20 Jul 2026 20:17:29 +0100 Subject: [PATCH 03/14] Update stubs/httpretty/httpretty/core.pyi Co-authored-by: Semyon Moroz --- stubs/httpretty/httpretty/core.pyi | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/stubs/httpretty/httpretty/core.pyi b/stubs/httpretty/httpretty/core.pyi index d421bba73e79..a6f52cf9d9c6 100644 --- a/stubs/httpretty/httpretty/core.pyi +++ b/stubs/httpretty/httpretty/core.pyi @@ -119,14 +119,14 @@ class URIMatcher: def get_next_entry(self, method: _HTTPMethod, info: URIInfo, request: HTTPrettyRequest) -> Entry: ... class httpretty(HttpBaseClass): - GET: Literal["GET"] - PUT: Literal["PUT"] - POST: Literal["POST"] - DELETE: Literal["DELETE"] - HEAD: Literal["HEAD"] - PATCH: Literal["PATCH"] - OPTIONS: Literal["OPTIONS"] - CONNECT: Literal["CONNECT"] + GET: Final = "GET" + PUT: Final = "PUT" + POST: Final = "POST" + DELETE: Final = "DELETE" + HEAD: Final = "HEAD" + PATCH: Final = "PATCH" + OPTIONS: Final = "OPTIONS" + CONNECT: Final = "CONNECT" METHODS: tuple[_HTTPMethod, ...] latest_requests: list[HTTPrettyRequest] last_request: HTTPrettyRequest | HTTPrettyRequestEmpty From 68595b76af520fda6a04e473d69768680b0aa7d3 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 20 Jul 2026 20:17:39 +0100 Subject: [PATCH 04/14] Update stubs/httpretty/httpretty/http.pyi Co-authored-by: Semyon Moroz --- stubs/httpretty/httpretty/http.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stubs/httpretty/httpretty/http.pyi b/stubs/httpretty/httpretty/http.pyi index 660b3545fa53..0398d13b8377 100644 --- a/stubs/httpretty/httpretty/http.pyi +++ b/stubs/httpretty/httpretty/http.pyi @@ -15,7 +15,7 @@ class HttpBaseClass: PATCH: Literal["PATCH"] OPTIONS: Literal["OPTIONS"] CONNECT: Literal["CONNECT"] - METHODS: tuple[Literal["GET", "PUT", "POST", "DELETE", "HEAD", "PATCH", "OPTIONS", "CONNECT"], ...] + METHODS: tuple[_HTTPMethod, ...] def parse_requestline(s: str) -> tuple[str, str, str]: ... def last_requestline(sent_data: Sequence[_T]) -> _T | None: ... From a92d182320d36b5cc4b53a8da712c63fff39c3f5 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 20 Jul 2026 20:17:47 +0100 Subject: [PATCH 05/14] Update stubs/httpretty/httpretty/http.pyi Co-authored-by: Semyon Moroz --- stubs/httpretty/httpretty/http.pyi | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/stubs/httpretty/httpretty/http.pyi b/stubs/httpretty/httpretty/http.pyi index 0398d13b8377..38feb43ee6c1 100644 --- a/stubs/httpretty/httpretty/http.pyi +++ b/stubs/httpretty/httpretty/http.pyi @@ -7,14 +7,14 @@ _T = TypeVar("_T", str, bytes) STATUSES: dict[int, str] class HttpBaseClass: - GET: Literal["GET"] - PUT: Literal["PUT"] - POST: Literal["POST"] - DELETE: Literal["DELETE"] - HEAD: Literal["HEAD"] - PATCH: Literal["PATCH"] - OPTIONS: Literal["OPTIONS"] - CONNECT: Literal["CONNECT"] + GET: Final = "GET" + PUT: Final = "PUT" + POST: Final = "POST" + DELETE: Final = "DELETE" + HEAD: Final = "HEAD" + PATCH: Final = "PATCH" + OPTIONS: Final = "OPTIONS" + CONNECT: Final = "CONNECT" METHODS: tuple[_HTTPMethod, ...] def parse_requestline(s: str) -> tuple[str, str, str]: ... From f4ffa52d9206a264775d2276524a70a023d6096d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:19:20 +0000 Subject: [PATCH 06/14] [pre-commit.ci] auto fixes from pre-commit.com hooks --- stubs/httpretty/httpretty/__init__.pyi | 2 -- 1 file changed, 2 deletions(-) diff --git a/stubs/httpretty/httpretty/__init__.pyi b/stubs/httpretty/httpretty/__init__.pyi index 10c32a092904..1bea66e0886d 100644 --- a/stubs/httpretty/httpretty/__init__.pyi +++ b/stubs/httpretty/httpretty/__init__.pyi @@ -1,5 +1,3 @@ -from typing import Literal - from .core import ( EmptyRequestHeaders as EmptyRequestHeaders, Entry as Entry, From c5308892899d5b6b1d810265d23c87f5533ea7fd Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 20 Jul 2026 20:44:42 +0100 Subject: [PATCH 07/14] Update stubs/httpretty/httpretty/core.pyi Co-authored-by: Semyon Moroz --- stubs/httpretty/httpretty/core.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stubs/httpretty/httpretty/core.pyi b/stubs/httpretty/httpretty/core.pyi index a6f52cf9d9c6..086327b4e642 100644 --- a/stubs/httpretty/httpretty/core.pyi +++ b/stubs/httpretty/httpretty/core.pyi @@ -24,7 +24,7 @@ class HTTPrettyRequest(HttpBaseClass): raw_headers: str path: str querystring: dict[str, list[str]] - parsed_body: Any + parsed_body: Any # It can be any object after parsing raw (str) body created_at: float def __init__( self, headers: str | bytes, body: _Body = "", sock: object | None = None, path_encoding: str = "iso-8859-1" From e955950e5abd0cd5c73ddb1ad2311b5cda1e0607 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Mon, 20 Jul 2026 20:44:53 +0100 Subject: [PATCH 08/14] Update stubs/httpretty/httpretty/core.pyi Co-authored-by: Semyon Moroz --- stubs/httpretty/httpretty/core.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stubs/httpretty/httpretty/core.pyi b/stubs/httpretty/httpretty/core.pyi index 086327b4e642..6a880997776a 100644 --- a/stubs/httpretty/httpretty/core.pyi +++ b/stubs/httpretty/httpretty/core.pyi @@ -76,7 +76,7 @@ class Entry(HttpBaseClass): ) -> None: ... def validate(self) -> None: ... def normalize_headers(self, headers: _Headers) -> dict[str, str]: ... - def fill_filekind(self, fk: Any) -> None: ... + def fill_filekind(self, fk: _WritableFileobj) -> None: ... class URIInfo(HttpBaseClass): default_str_attrs: tuple[str, ...] From ad3d18274fc066ac9da0232bbbeab309c482daa0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:46:46 +0000 Subject: [PATCH 09/14] [pre-commit.ci] auto fixes from pre-commit.com hooks --- stubs/httpretty/httpretty/http.pyi | 1 - 1 file changed, 1 deletion(-) diff --git a/stubs/httpretty/httpretty/http.pyi b/stubs/httpretty/httpretty/http.pyi index 38feb43ee6c1..c6669544ce5d 100644 --- a/stubs/httpretty/httpretty/http.pyi +++ b/stubs/httpretty/httpretty/http.pyi @@ -1,5 +1,4 @@ from collections.abc import Sequence -from typing import Literal from typing_extensions import TypeVar _T = TypeVar("_T", str, bytes) From 37cbe7141eaef57d600310f7ce2bee19d67db817 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 20 Aug 2026 11:55:02 +0100 Subject: [PATCH 10/14] Fix httpretty stubs: import Final/aliases and address review Import Final and define _HTTPMethod/_WritableFileobj so mypy/stubtest pass; apply reviewer suggestions for Protocols and comments. Co-authored-by: Cursor --- stubs/httpretty/httpretty/__init__.pyi | 4 +++- stubs/httpretty/httpretty/core.pyi | 14 ++++++++++---- stubs/httpretty/httpretty/http.pyi | 2 ++ stubs/httpretty/httpretty/version.pyi | 4 +++- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/stubs/httpretty/httpretty/__init__.pyi b/stubs/httpretty/httpretty/__init__.pyi index 1bea66e0886d..22b0f3a9b48d 100644 --- a/stubs/httpretty/httpretty/__init__.pyi +++ b/stubs/httpretty/httpretty/__init__.pyi @@ -1,3 +1,5 @@ +from typing import Final + from .core import ( EmptyRequestHeaders as EmptyRequestHeaders, Entry as Entry, @@ -13,7 +15,7 @@ from .core import ( ) from .errors import HTTPrettyError as HTTPrettyError, UnmockedError as UnmockedError -__version__: str +__version__: Final[str] HTTPretty = httpretty activate = httprettified enabled = httprettized diff --git a/stubs/httpretty/httpretty/core.pyi b/stubs/httpretty/httpretty/core.pyi index 6a880997776a..d43fd55144a4 100644 --- a/stubs/httpretty/httpretty/core.pyi +++ b/stubs/httpretty/httpretty/core.pyi @@ -3,13 +3,18 @@ from collections.abc import Callable, Iterable, Mapping from contextlib import AbstractContextManager from http.client import HTTPMessage from types import TracebackType -from typing import Any, Literal, TypeAlias, overload +from typing import Any, Final, Protocol, TypeAlias, overload, type_check_only from typing_extensions import ParamSpec -from .http import HttpBaseClass +from .http import HttpBaseClass, _HTTPMethod _P = ParamSpec("_P") -_HTTPMethod: TypeAlias = Literal["GET", "PUT", "POST", "DELETE", "HEAD", "PATCH", "OPTIONS", "CONNECT"] + +@type_check_only +class _WritableFileobj(Protocol): + def write(self, b: bytes, /) -> object: ... + def seek(self, offset: int, /) -> object: ... + _URI: TypeAlias = str | re.Pattern[str] _HeaderValue: TypeAlias = str | int | bool | None _Headers: TypeAlias = Mapping[str, _HeaderValue] @@ -44,7 +49,7 @@ class HTTPrettyRequest(HttpBaseClass): @property def host(self) -> str: ... def parse_querystring(self, qs: str) -> dict[str, list[str]]: ... - def parse_request_body(self, body: str) -> Any: ... + def parse_request_body(self, body: str) -> Any: ... # Any object can be returned if deserialization is successful class EmptyRequestHeaders(dict[str, str]): ... @@ -145,6 +150,7 @@ class httpretty(HttpBaseClass): encoding: str = "utf-8", verbose: bool = False, allow_net_connect: bool = True, + # Passed to urllib3.PoolManager as kwargs, and connection pool's parameters have various types pool_manager_params: Mapping[str, Any] | None = None, ) -> AbstractContextManager[None]: ... @classmethod diff --git a/stubs/httpretty/httpretty/http.pyi b/stubs/httpretty/httpretty/http.pyi index c6669544ce5d..b529b70981ec 100644 --- a/stubs/httpretty/httpretty/http.pyi +++ b/stubs/httpretty/httpretty/http.pyi @@ -1,7 +1,9 @@ from collections.abc import Sequence +from typing import Final, Literal, TypeAlias from typing_extensions import TypeVar _T = TypeVar("_T", str, bytes) +_HTTPMethod: TypeAlias = Literal["GET", "PUT", "POST", "DELETE", "HEAD", "PATCH", "OPTIONS", "CONNECT"] STATUSES: dict[int, str] diff --git a/stubs/httpretty/httpretty/version.pyi b/stubs/httpretty/httpretty/version.pyi index c2ee2cab489b..669513528d4a 100644 --- a/stubs/httpretty/httpretty/version.pyi +++ b/stubs/httpretty/httpretty/version.pyi @@ -1 +1,3 @@ -version: str +from typing import Final + +version: Final[str] From 254b997ee355782d48044723b33a52aa7382e72f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 20 Aug 2026 11:58:08 +0100 Subject: [PATCH 11/14] Avoid redeclaring Final HTTP method attrs on httpretty HttpBaseClass already marks them Final; subclass overrides fail mypy. Co-authored-by: Cursor --- stubs/httpretty/httpretty/core.pyi | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/stubs/httpretty/httpretty/core.pyi b/stubs/httpretty/httpretty/core.pyi index d43fd55144a4..d1cde636514e 100644 --- a/stubs/httpretty/httpretty/core.pyi +++ b/stubs/httpretty/httpretty/core.pyi @@ -3,7 +3,7 @@ from collections.abc import Callable, Iterable, Mapping from contextlib import AbstractContextManager from http.client import HTTPMessage from types import TracebackType -from typing import Any, Final, Protocol, TypeAlias, overload, type_check_only +from typing import Any, Protocol, TypeAlias, overload, type_check_only from typing_extensions import ParamSpec from .http import HttpBaseClass, _HTTPMethod @@ -124,14 +124,6 @@ class URIMatcher: def get_next_entry(self, method: _HTTPMethod, info: URIInfo, request: HTTPrettyRequest) -> Entry: ... class httpretty(HttpBaseClass): - GET: Final = "GET" - PUT: Final = "PUT" - POST: Final = "POST" - DELETE: Final = "DELETE" - HEAD: Final = "HEAD" - PATCH: Final = "PATCH" - OPTIONS: Final = "OPTIONS" - CONNECT: Final = "CONNECT" METHODS: tuple[_HTTPMethod, ...] latest_requests: list[HTTPrettyRequest] last_request: HTTPrettyRequest | HTTPrettyRequestEmpty From d767b45a059468dcfef66b8adeab6cf045649e3f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 20 Aug 2026 12:21:12 +0100 Subject: [PATCH 12/14] Remove unnecessary httpretty regression tests --- .../httpretty/@tests/test_cases/check_usage.py | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 stubs/httpretty/@tests/test_cases/check_usage.py diff --git a/stubs/httpretty/@tests/test_cases/check_usage.py b/stubs/httpretty/@tests/test_cases/check_usage.py deleted file mode 100644 index 664d8dfbf7fa..000000000000 --- a/stubs/httpretty/@tests/test_cases/check_usage.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing_extensions import assert_type - -import httpretty - - -@httpretty.activate(allow_net_connect=False) -def decorated() -> int: - httpretty.register_uri(httpretty.GET, "https://example.com/", body="ok", status=200, content_type="text/plain") - assert_type(httpretty.last_request(), httpretty.HTTPrettyRequest | httpretty.HTTPrettyRequestEmpty) - assert_type(httpretty.latest_requests(), list[httpretty.HTTPrettyRequest]) - assert_type(httpretty.has_request(), bool) - return 1 - - -with httpretty.enabled(allow_net_connect=False): - response = httpretty.Response("ok", status=201) - assert_type(response, httpretty.Entry) From a0a55f0a21f3821a20970960c953d2f20b45d15a Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 20 Aug 2026 12:21:22 +0100 Subject: [PATCH 13/14] Preserve return type in httprettified --- stubs/httpretty/httpretty/core.pyi | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/stubs/httpretty/httpretty/core.pyi b/stubs/httpretty/httpretty/core.pyi index d1cde636514e..d858deefecdb 100644 --- a/stubs/httpretty/httpretty/core.pyi +++ b/stubs/httpretty/httpretty/core.pyi @@ -4,11 +4,12 @@ from contextlib import AbstractContextManager from http.client import HTTPMessage from types import TracebackType from typing import Any, Protocol, TypeAlias, overload, type_check_only -from typing_extensions import ParamSpec +from typing_extensions import ParamSpec, TypeVar from .http import HttpBaseClass, _HTTPMethod _P = ParamSpec("_P") +_R = TypeVar("_R") @type_check_only class _WritableFileobj(Protocol): @@ -194,8 +195,8 @@ class httprettized: ) -> None: ... @overload -def httprettified(test: Callable[_P, Any]) -> Callable[_P, Any]: ... +def httprettified(test: Callable[_P, _R]) -> Callable[_P, _R]: ... @overload def httprettified( test: None = None, allow_net_connect: bool = True, verbose: bool = False -) -> Callable[[Callable[_P, Any]], Callable[_P, Any]]: ... +) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ... From c267ac4a4c39e3277dcf1911908bfb6cb176b7c7 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Thu, 20 Aug 2026 12:21:31 +0100 Subject: [PATCH 14/14] Accept reversible data in last_requestline --- stubs/httpretty/httpretty/http.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stubs/httpretty/httpretty/http.pyi b/stubs/httpretty/httpretty/http.pyi index b529b70981ec..a3d2612cb381 100644 --- a/stubs/httpretty/httpretty/http.pyi +++ b/stubs/httpretty/httpretty/http.pyi @@ -1,4 +1,4 @@ -from collections.abc import Sequence +from _typeshed import SupportsLenAndGetItem from typing import Final, Literal, TypeAlias from typing_extensions import TypeVar @@ -19,4 +19,4 @@ class HttpBaseClass: METHODS: tuple[_HTTPMethod, ...] def parse_requestline(s: str) -> tuple[str, str, str]: ... -def last_requestline(sent_data: Sequence[_T]) -> _T | None: ... +def last_requestline(sent_data: SupportsLenAndGetItem[_T]) -> _T | None: ...