From 7a27540236c7fe4b02a6c33bf16371516532ffc2 Mon Sep 17 00:00:00 2001 From: Hayao Date: Thu, 29 Jan 2026 10:30:34 +0900 Subject: [PATCH 1/4] Add type annotations --- pytest_httpserver/blocking_httpserver.py | 14 ++-- pytest_httpserver/hooks.py | 8 +- pytest_httpserver/httpserver.py | 101 +++++++++++------------ pytest_httpserver/pytest_plugin.py | 45 ++++++---- tests/test_hooks.py | 8 +- tests/test_log_querying.py | 4 +- 6 files changed, 96 insertions(+), 84 deletions(-) diff --git a/pytest_httpserver/blocking_httpserver.py b/pytest_httpserver/blocking_httpserver.py index 31ed7862..096e9e42 100644 --- a/pytest_httpserver/blocking_httpserver.py +++ b/pytest_httpserver/blocking_httpserver.py @@ -29,10 +29,10 @@ class BlockingRequestHandler(RequestHandlerBase): This class should only be instantiated inside the implementation of the :py:class:`BlockingHTTPServer`. """ - def __init__(self): - self.response_queue = Queue() + def __init__(self) -> None: + self.response_queue: Queue[Response] = Queue() - def respond_with_response(self, response: Response): + def respond_with_response(self, response: Response) -> None: self.response_queue.put_nowait(response) @@ -59,11 +59,11 @@ class BlockingHTTPServer(HTTPServerBase): def __init__( self, - host=DEFAULT_LISTEN_HOST, - port=DEFAULT_LISTEN_PORT, + host: str = DEFAULT_LISTEN_HOST, + port: int = DEFAULT_LISTEN_PORT, ssl_context: SSLContext | None = None, timeout: int = 30, - ): + ) -> None: super().__init__(host, port, ssl_context) self.timeout = timeout self.request_queue: Queue[Request] = Queue() @@ -76,7 +76,7 @@ def assert_request( data: str | bytes | None = None, data_encoding: str = "utf-8", headers: Mapping[str, str] | None = None, - query_string: None | QueryMatcher | str | bytes | Mapping = None, + query_string: None | QueryMatcher | str | bytes | Mapping[str, str] = None, header_value_matcher: HeaderValueMatcher | None = None, json: Any = UNDEFINED, timeout: int = 30, diff --git a/pytest_httpserver/hooks.py b/pytest_httpserver/hooks.py index e997e526..e995a6b4 100644 --- a/pytest_httpserver/hooks.py +++ b/pytest_httpserver/hooks.py @@ -20,7 +20,7 @@ class Chain: similar to reduce. """ - def __init__(self, *args: Callable[[Request, Response], Response]): + def __init__(self, *args: Callable[[Request, Response], Response]) -> None: """ :param *args: callable objects specified in the same order they should be called. @@ -43,13 +43,13 @@ class Delay: Delays returning the response """ - def __init__(self, seconds: float): + def __init__(self, seconds: float) -> None: """ :param seconds: seconds to sleep before returning the response """ self._seconds = seconds - def _sleep(self): + def _sleep(self) -> None: """ Sleeps for the seconds specified in the constructor """ @@ -65,7 +65,7 @@ def __call__(self, _request: Request, response: Response) -> Response: class Garbage: - def __init__(self, prefix_size: int = 0, suffix_size: int = 0): + def __init__(self, prefix_size: int = 0, suffix_size: int = 0) -> None: """ Adds random bytes to the beginning or to the end of the response data. diff --git a/pytest_httpserver/httpserver.py b/pytest_httpserver/httpserver.py index 5c1b98c3..f255bb78 100644 --- a/pytest_httpserver/httpserver.py +++ b/pytest_httpserver/httpserver.py @@ -9,6 +9,7 @@ import time import urllib.parse from collections import defaultdict +from collections.abc import Generator from collections.abc import Iterable from collections.abc import Mapping from collections.abc import MutableMapping @@ -49,7 +50,7 @@ class Undefined: - def __repr__(self): + def __repr__(self) -> str: return "" @@ -93,7 +94,7 @@ def __init__( raise_assertions: bool = True, # noqa: FBT001 stop_on_nohandler: bool = True, # noqa: FBT001 timeout: float = 5, - ): + ) -> None: self.raise_assertions = raise_assertions self.stop_on_nohandler = stop_on_nohandler self.timeout = timeout @@ -104,12 +105,12 @@ class Waiting: This class should not be instantiated directly.""" - def __init__(self): - self._result = None + def __init__(self) -> None: + self._result: bool | None = None self._start = time.monotonic() - self._stop = None + self._stop: float | None = None - def complete(self, result: bool): # noqa: FBT001 + def complete(self, result: bool) -> None: # noqa: FBT001 self._result = result self._stop = time.monotonic() @@ -135,7 +136,7 @@ class HeaderValueMatcher: DEFAULT_MATCHERS: ClassVar[MutableMapping[str, Callable[[str | None, str], bool]]] = {} - def __init__(self, matchers: Mapping[str, Callable[[str | None, str], bool]] | None = None): + def __init__(self, matchers: Mapping[str, Callable[[str | None, str], bool]] | None = None) -> None: self.matchers = self.DEFAULT_MATCHERS if matchers is None else matchers @staticmethod @@ -175,7 +176,7 @@ class QueryMatcher(abc.ABC): def match(self, request_query_string: bytes) -> bool: values = self.get_comparing_values(request_query_string) - return values[0] == values[1] + return bool(values[0] == values[1]) @abc.abstractmethod def get_comparing_values(self, request_query_string: bytes) -> tuple[Any, Any]: @@ -187,7 +188,7 @@ class StringQueryMatcher(QueryMatcher): Matches a query for a string or bytes specified """ - def __init__(self, query_string: bytes | str): + def __init__(self, query_string: bytes | str) -> None: """ :param query_string: the query string will be compared to this string or bytes. If string is specified, it will be encoded by the encode() method. @@ -202,10 +203,8 @@ def __init__(self, query_string: bytes | str): def get_comparing_values(self, request_query_string: bytes) -> tuple[bytes, bytes]: if isinstance(self.query_string, str): query_string = self.query_string.encode() - elif isinstance(self.query_string, bytes): # type: ignore - query_string = self.query_string else: - raise TypeError("query_string must be a string, or a bytes-like object") + query_string = self.query_string return (request_query_string, query_string) @@ -215,7 +214,7 @@ class MappingQueryMatcher(QueryMatcher): Matches a query string to a dictionary or MultiDict specified """ - def __init__(self, query_dict: Mapping[str, str] | MultiDict[str, str]): + def __init__(self, query_dict: Mapping[str, str] | MultiDict[str, str]) -> None: """ :param query_dict: if dictionary (Mapping) is specified, it will be used as a key-value mapping where both key and value should be string. If there are multiple @@ -238,14 +237,14 @@ class BooleanQueryMatcher(QueryMatcher): Matches the query depending on the boolean value """ - def __init__(self, result: bool): # noqa: FBT001 + def __init__(self, result: bool) -> None: # noqa: FBT001 """ :param result: if this parameter is true, the query match will be always successful. Otherwise, no query match will be successful. """ self.result = result - def get_comparing_values(self, request_query_string: bytes): # noqa: ARG002 + def get_comparing_values(self, request_query_string: bytes) -> tuple[bool, bool]: # noqa: ARG002 if self.result: return (True, True) else: @@ -319,7 +318,7 @@ def __init__( query_string: None | QueryMatcher | str | bytes | Mapping[str, str] = None, header_value_matcher: HVMATCHER_T | None = None, json: Any = UNDEFINED, - ): + ) -> None: if json is not UNDEFINED and data is not None: raise ValueError("data and json parameters are mutually exclusive") @@ -344,7 +343,7 @@ def __init__( if header_value_matcher is not None: self.header_value_matcher = header_value_matcher - def __repr__(self): + def __repr__(self) -> str: """ Returns the string representation of the object, with the known parameters. """ @@ -412,7 +411,7 @@ def match_json(self, request: Request) -> bool: except UnicodeDecodeError: return False - return json_received == self.json + return bool(json_received == self.json) def difference(self, request: Request) -> list[tuple[str, str, str | URIPattern]]: """ @@ -475,7 +474,7 @@ def respond_with_json( status: int = 200, headers: Mapping[str, str] | None = None, content_type: str = "application/json", - ): + ) -> None: """ Prepares a response with a serialized JSON object. @@ -495,7 +494,7 @@ def respond_with_data( headers: HEADERS_T | None = None, mimetype: str | None = None, content_type: str | None = None, - ): + ) -> None: """ Prepares a response with raw data. @@ -512,7 +511,7 @@ def respond_with_data( self.respond_with_response(Response(response_data, status, headers, mimetype, content_type)) @abc.abstractmethod - def respond_with_response(self, response: Response): + def respond_with_response(self, response: Response) -> None: """ Prepares a response with the specified response object. @@ -530,12 +529,12 @@ class RequestHandler(RequestHandlerBase): :param matcher: the matcher object """ - def __init__(self, matcher: RequestMatcher): + def __init__(self, matcher: RequestMatcher) -> None: self.matcher = matcher self.request_handler: Callable[[Request], Response] | None = None self._hooks: list[Callable[[Request, Response], Response]] = [] - def with_post_hook(self, hook: Callable[[Request, Response], Response]): + def with_post_hook(self, hook: Callable[[Request, Response], Response]) -> RequestHandler: self._hooks.append(hook) return self @@ -561,7 +560,7 @@ def respond(self, request: Request) -> Response: response = hook(request, response) return response - def respond_with_handler(self, func: Callable[[Request], Response]): + def respond_with_handler(self, func: Callable[[Request], Response]) -> None: """ Registers the specified function as a responder. @@ -569,7 +568,7 @@ def respond_with_handler(self, func: Callable[[Request], Response]): """ self.request_handler = func - def respond_with_response(self, response: Response): + def respond_with_response(self, response: Response) -> None: self.request_handler = lambda request: response def __repr__(self) -> str: @@ -635,7 +634,7 @@ def __init__( ssl_context: SSLContext | None = None, *, threaded: bool = False, - ): + ) -> None: """ Initializes the instance. @@ -651,10 +650,10 @@ def __init__( self.threaded = threaded self.no_handler_status_code = 500 - def __repr__(self): + def __repr__(self) -> str: return f"<{self.__class__.__name__} host={self.host} port={self.port}>" - def clear(self): + def clear(self) -> None: """ Clears and resets the state attributes of the object. @@ -666,28 +665,28 @@ def clear(self): self.clear_log() self.no_handler_status_code = 500 - def clear_assertions(self): + def clear_assertions(self) -> None: """ Clears the list of assertions """ self.assertions = [] - def clear_handler_errors(self): + def clear_handler_errors(self) -> None: """ Clears the list of collected errors from handler invocations """ self.handler_errors = [] - def clear_log(self): + def clear_log(self) -> None: """ Clears the list of log entries """ self.log = [] - def url_for(self, suffix: str): + def url_for(self, suffix: str) -> str: """ Return an url for a given suffix. @@ -716,7 +715,7 @@ def url_for(self, suffix: str): return "{}://{}:{}{}".format(protocol, host, self.port, suffix) - def create_matcher(self, *args, **kwargs) -> RequestMatcher: + def create_matcher(self, *args: Any, **kwargs: Any) -> RequestMatcher: """ Creates a :py:class:`.RequestMatcher` instance with the specified parameters. @@ -725,7 +724,7 @@ def create_matcher(self, *args, **kwargs) -> RequestMatcher: return RequestMatcher(*args, **kwargs) - def thread_target(self): + def thread_target(self) -> None: """ This method serves as a thread target when the server is started. @@ -774,7 +773,7 @@ def start(self) -> None: self.server_thread = threading.Thread(target=self.thread_target, daemon=True) self.server_thread.start() - def stop(self): + def stop(self) -> None: """ Stop the running server. @@ -793,7 +792,7 @@ def stop(self): self.server = None self.server_thread = None - def add_assertion(self, obj: str | AssertionError): + def add_assertion(self, obj: str | AssertionError) -> None: """ Add a new assertion @@ -804,7 +803,7 @@ def add_assertion(self, obj: str | AssertionError): """ self.assertions.append(obj) - def check(self): + def check(self) -> None: """ Raises AssertionError or Errors raised in handlers. @@ -813,7 +812,7 @@ def check(self): self.check_assertions() self.check_handler_errors() - def check_assertions(self): + def check_assertions(self) -> None: """ Raise AssertionError when at least one assertion added @@ -831,7 +830,7 @@ def check_assertions(self): raise AssertionError(assertion) - def check_handler_errors(self): + def check_handler_errors(self) -> None: """ Re-Raises any errors caused in request handlers @@ -841,7 +840,7 @@ def check_handler_errors(self): if self.handler_errors: raise self.handler_errors.pop(0) - def respond_nohandler(self, request: Request, extra_message: str = ""): + def respond_nohandler(self, request: Request, extra_message: str = "") -> Response: """ Add a 'no handler' assertion. @@ -877,7 +876,7 @@ def application(self, request: Request) -> Response: self.log.append((request, response)) return response - def __enter__(self): + def __enter__(self) -> HTTPServerBase: # noqa: PYI034 """ Provide the context API @@ -893,7 +892,7 @@ def __exit__( exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, - ): + ) -> None: """ Provide the context API @@ -904,7 +903,7 @@ def __exit__( self.stop() @staticmethod - def format_host(host: str): + def format_host(host: str) -> str: """ Formats a hostname so it can be used in a URL. Notably, this adds brackets around IPV6 addresses when @@ -953,7 +952,7 @@ def __init__( default_waiting_settings: WaitingSettings | None = None, *, threaded: bool = False, - ): + ) -> None: """ Initializes the instance. """ @@ -970,7 +969,7 @@ def __init__( self._waiting_settings = copy(self.default_waiting_settings) self._waiting_result: queue.LifoQueue[bool] = queue.LifoQueue(maxsize=1) - def clear(self): + def clear(self) -> None: """ Clears and resets the state attributes of the object. @@ -981,7 +980,7 @@ def clear(self): self.clear_all_handlers() self.permanently_failed = False - def clear_all_handlers(self): + def clear_all_handlers(self) -> None: """ Clears all types of the handlers (ordered, oneshot, permanent) """ @@ -1209,7 +1208,7 @@ def format_matchers(self) -> str: This method is primarily used when reporting errors. """ - def format_handlers(handlers: list[RequestHandler]): + def format_handlers(handlers: list[RequestHandler]) -> list[str]: if handlers: return [" {!r}".format(handler.matcher) for handler in handlers] else: @@ -1227,7 +1226,7 @@ def format_handlers(handlers: list[RequestHandler]): return "\n".join(lines) - def respond_nohandler(self, request: Request, extra_message: str = ""): + def respond_nohandler(self, request: Request, extra_message: str = "") -> Response: """ Add a 'no handler' assertion. @@ -1240,7 +1239,7 @@ def respond_nohandler(self, request: Request, extra_message: str = ""): return super().respond_nohandler(request, self.format_matchers() + extra_message) - def respond_permanent_failure(self): + def respond_permanent_failure(self) -> Response: """ Add a 'permanent failure' assertion. @@ -1333,7 +1332,7 @@ def wait( raise_assertions: bool | None = None, # noqa: FBT001 stop_on_nohandler: bool | None = None, # noqa: FBT001 timeout: float | None = None, - ): + ) -> Generator[Waiting, None, None]: """Context manager to wait until the first of following event occurs: all ordered and oneshot handlers were executed, unexpected request was received (if `stop_on_nohandler` is set to `True`), or time was out @@ -1410,7 +1409,7 @@ def get_matching_requests_count(self, matcher: RequestMatcher) -> int: """ return len(list(self.iter_matching_requests(matcher))) - def assert_request_made(self, matcher: RequestMatcher, *, count: int = 1): + def assert_request_made(self, matcher: RequestMatcher, *, count: int = 1) -> None: """ Check the amount of log entries matching for the matcher specified. By default it verifies that exactly one request matching for the matcher diff --git a/pytest_httpserver/pytest_plugin.py b/pytest_httpserver/pytest_plugin.py index 9b484dfb..f19ec588 100644 --- a/pytest_httpserver/pytest_plugin.py +++ b/pytest_httpserver/pytest_plugin.py @@ -1,45 +1,54 @@ +from __future__ import annotations + import os +from typing import TYPE_CHECKING import pytest from .httpserver import HTTPServer +if TYPE_CHECKING: + from collections.abc import Generator + from ssl import SSLContext + class Plugin: - SERVER = None + SERVER: PluginHTTPServer | None = None class PluginHTTPServer(HTTPServer): - def start(self): + def start(self) -> None: super().start() Plugin.SERVER = self - def stop(self): + def stop(self) -> None: super().stop() Plugin.SERVER = None -def get_httpserver_listen_address(): +def get_httpserver_listen_address() -> tuple[str | None, int | None]: listen_host = os.environ.get("PYTEST_HTTPSERVER_HOST") - listen_port = os.environ.get("PYTEST_HTTPSERVER_PORT") - if listen_port: - listen_port = int(listen_port) + listen_port_str = os.environ.get("PYTEST_HTTPSERVER_PORT") + listen_port: int | None = int(listen_port_str) if listen_port_str else None return listen_host, listen_port @pytest.fixture(scope="session") -def httpserver_listen_address(): +def httpserver_listen_address() -> tuple[str | None, int | None]: return get_httpserver_listen_address() @pytest.fixture(scope="session") -def httpserver_ssl_context(): +def httpserver_ssl_context() -> None: return None @pytest.fixture(scope="session") -def make_httpserver(httpserver_listen_address, httpserver_ssl_context): +def make_httpserver( + httpserver_listen_address: tuple[str | None, int | None], + httpserver_ssl_context: SSLContext | None, +) -> Generator[HTTPServer, None, None]: host, port = httpserver_listen_address if not host: host = HTTPServer.DEFAULT_LISTEN_HOST @@ -54,7 +63,7 @@ def make_httpserver(httpserver_listen_address, httpserver_ssl_context): server.stop() -def pytest_sessionfinish(session, exitstatus): # noqa: ARG001 +def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None: # noqa: ARG001 if Plugin.SERVER is not None: Plugin.SERVER.clear() if Plugin.SERVER.is_running(): @@ -62,14 +71,16 @@ def pytest_sessionfinish(session, exitstatus): # noqa: ARG001 @pytest.fixture -def httpserver(make_httpserver): +def httpserver(make_httpserver: HTTPServer) -> HTTPServer: server = make_httpserver server.clear() return server @pytest.fixture(scope="session") -def make_httpserver_ipv4(httpserver_ssl_context): +def make_httpserver_ipv4( + httpserver_ssl_context: SSLContext | None, +) -> Generator[HTTPServer, None, None]: server = HTTPServer(host="127.0.0.1", port=0, ssl_context=httpserver_ssl_context) server.start() yield server @@ -79,14 +90,16 @@ def make_httpserver_ipv4(httpserver_ssl_context): @pytest.fixture -def httpserver_ipv4(make_httpserver_ipv4): +def httpserver_ipv4(make_httpserver_ipv4: HTTPServer) -> HTTPServer: server = make_httpserver_ipv4 server.clear() return server @pytest.fixture(scope="session") -def make_httpserver_ipv6(httpserver_ssl_context): +def make_httpserver_ipv6( + httpserver_ssl_context: SSLContext | None, +) -> Generator[HTTPServer, None, None]: server = HTTPServer(host="::1", port=0, ssl_context=httpserver_ssl_context) server.start() yield server @@ -96,7 +109,7 @@ def make_httpserver_ipv6(httpserver_ssl_context): @pytest.fixture -def httpserver_ipv6(make_httpserver_ipv6): +def httpserver_ipv6(make_httpserver_ipv6: HTTPServer) -> HTTPServer: server = make_httpserver_ipv6 server.clear() return server diff --git a/tests/test_hooks.py b/tests/test_hooks.py index b093ad08..a61ed831 100644 --- a/tests/test_hooks.py +++ b/tests/test_hooks.py @@ -17,11 +17,11 @@ class MyDelay(Delay): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.evidence: int | None = None + def __init__(self, seconds: float) -> None: + super().__init__(seconds) + self.evidence: float | None = None - def _sleep(self): + def _sleep(self) -> None: assert self.evidence is None, "_sleep should be called only once" self.evidence = self._seconds diff --git a/tests/test_log_querying.py b/tests/test_log_querying.py index 1960800d..1222a99e 100644 --- a/tests/test_log_querying.py +++ b/tests/test_log_querying.py @@ -26,10 +26,10 @@ def test_verify(httpserver: HTTPServer): httpserver.assert_request_made(httpserver.create_matcher("/no_match"), count=0) with pytest.raises(AssertionError): - assert httpserver.assert_request_made(httpserver.create_matcher("/no_match")) + httpserver.assert_request_made(httpserver.create_matcher("/no_match")) with pytest.raises(AssertionError): - assert httpserver.assert_request_made(httpserver.create_matcher("/foo"), count=2) + httpserver.assert_request_made(httpserver.create_matcher("/foo"), count=2) def test_verify_assert_msg(httpserver: HTTPServer): From 40cdf7dc13e963fd79ee1a2c75bc91d57af28baa Mon Sep 17 00:00:00 2001 From: Hayao Date: Fri, 30 Jan 2026 07:19:25 +0900 Subject: [PATCH 2/4] Restore query_string type check in StringQueryMatcher --- pytest_httpserver/httpserver.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pytest_httpserver/httpserver.py b/pytest_httpserver/httpserver.py index f255bb78..16849b8c 100644 --- a/pytest_httpserver/httpserver.py +++ b/pytest_httpserver/httpserver.py @@ -201,6 +201,9 @@ def __init__(self, query_string: bytes | str) -> None: self.query_string = query_string def get_comparing_values(self, request_query_string: bytes) -> tuple[bytes, bytes]: + if not isinstance(self.query_string, (str, bytes)): + raise TypeError("query_string must be a string, or a bytes-like object") + if isinstance(self.query_string, str): query_string = self.query_string.encode() else: From 95368c6ec9b85ee46847da7a13ffcbc5213bb451 Mon Sep 17 00:00:00 2001 From: Hayao Date: Fri, 30 Jan 2026 07:30:22 +0900 Subject: [PATCH 3/4] Change __enter__ return type to Self --- poetry.lock | 6 +++--- pyproject.toml | 1 + pytest_httpserver/httpserver.py | 8 +++++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 162ed2db..a9900b3f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.1 and should not be changed by hand. [[package]] name = "alabaster" @@ -1411,7 +1411,7 @@ version = "2.6.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["dev", "develop", "doc", "test"] +groups = ["develop", "doc", "test"] files = [ {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, @@ -1486,4 +1486,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.9" -content-hash = "e20151feca6b3b3918563e7c5ac7983c28d7d442a98b2ab22dd5c44dc7c428cb" +content-hash = "20492e635a0a2507cd0bd41bf60a5c293171cbb5b94db18d733b75018669a1d4" diff --git a/pyproject.toml b/pyproject.toml index acdd0b48..6628f638 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ optional = true [tool.poetry.group.develop.dependencies] pre-commit = ">=2.20,<5.0" requests = "*" +typing_extensions = { version = ">=4.0", markers = "python_version < '3.11'" } Sphinx = ">=5.1.1,<8.0.0" sphinx-rtd-theme = ">=1,<4" reno = "*" diff --git a/pytest_httpserver/httpserver.py b/pytest_httpserver/httpserver.py index 16849b8c..cbcaaf22 100644 --- a/pytest_httpserver/httpserver.py +++ b/pytest_httpserver/httpserver.py @@ -33,11 +33,17 @@ from werkzeug.serving import make_server if TYPE_CHECKING: + import sys from ssl import SSLContext from types import TracebackType from werkzeug.serving import BaseWSGIServer + if sys.version_info >= (3, 11): + from typing import Self + else: + from typing_extensions import Self + URI_DEFAULT = "" METHOD_ALL = "__ALL" @@ -879,7 +885,7 @@ def application(self, request: Request) -> Response: self.log.append((request, response)) return response - def __enter__(self) -> HTTPServerBase: # noqa: PYI034 + def __enter__(self) -> Self: """ Provide the context API From e0879d10b372aee41b3592f8c4ab30d798fdf24d Mon Sep 17 00:00:00 2001 From: Hayao Date: Fri, 30 Jan 2026 07:30:45 +0900 Subject: [PATCH 4/4] Bump PY_MAX_VERSION to 3.14 --- tests/test_release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_release.py b/tests/test_release.py index 3cba8d42..bc399076 100644 --- a/tests/test_release.py +++ b/tests/test_release.py @@ -29,7 +29,7 @@ NAME = "pytest-httpserver" NAME_UNDERSCORE = NAME.replace("-", "_") -PY_MAX_VERSION = (3, 13) +PY_MAX_VERSION = (3, 14) @pytest.fixture(scope="session")