From 485e870fda25dd3f1ae408140857e7243ebd3e11 Mon Sep 17 00:00:00 2001 From: Cserna Zsolt Date: Mon, 2 Feb 2026 19:36:56 +0100 Subject: [PATCH 1/4] Fixture based server options Enable users to specify options for the server via fixtures. We use a dataclass which keeps the options (so it is extendable later on), which is returned by a fixture. This fixture can be overridden by the user to customize the options. The other options: host, port, ssl_context are still provided by their own fixtures and this is kept for backward compatibility. --- pytest_httpserver/__init__.py | 2 ++ pytest_httpserver/httpserver.py | 21 +++++++++++++++ pytest_httpserver/pytest_plugin.py | 30 +++++++++++++++++++--- tests/test_extra_options.py | 41 ++++++++++++++++++++++++++++++ tests/test_release.py | 1 + 5 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 tests/test_extra_options.py diff --git a/pytest_httpserver/__init__.py b/pytest_httpserver/__init__.py index e81e58a..d132ecf 100644 --- a/pytest_httpserver/__init__.py +++ b/pytest_httpserver/__init__.py @@ -10,6 +10,7 @@ "BlockingHTTPServer", "BlockingRequestHandler", "Error", + "ExtraOptions", "HTTPServer", "HTTPServerError", "HeaderValueMatcher", @@ -27,6 +28,7 @@ from .httpserver import METHOD_ALL from .httpserver import URI_DEFAULT from .httpserver import Error +from .httpserver import ExtraOptions from .httpserver import HeaderValueMatcher from .httpserver import HTTPServer from .httpserver import HTTPServerError diff --git a/pytest_httpserver/httpserver.py b/pytest_httpserver/httpserver.py index 110c428..19eb1e9 100644 --- a/pytest_httpserver/httpserver.py +++ b/pytest_httpserver/httpserver.py @@ -18,6 +18,7 @@ from contextlib import contextmanager from contextlib import suppress from copy import copy +from dataclasses import dataclass from enum import Enum from http import HTTPStatus from re import Pattern @@ -947,6 +948,13 @@ def format_host(host: str) -> str: return host +@dataclass +class ExtraOptions: + default_waiting_settings: WaitingSettings | None = None + threaded: bool = False + startup_timeout: float | None = None + + class HTTPServer(HTTPServerBase): # pylint: disable=too-many-instance-attributes """ Server instance which manages handlers to serve pre-defined requests. @@ -1001,6 +1009,19 @@ def __init__( self.startup_timeout = startup_timeout self._readiness_check_pending = False + @classmethod + def with_extra_options( + cls, host: str, port: int, ssl_context: SSLContext | None, extra_options: ExtraOptions + ) -> Self: + return cls( + host, + port, + ssl_context, + default_waiting_settings=extra_options.default_waiting_settings, + threaded=extra_options.threaded, + startup_timeout=extra_options.startup_timeout, + ) + def start(self) -> None: super().start() self._readiness_check_pending = self.startup_timeout is not None diff --git a/pytest_httpserver/pytest_plugin.py b/pytest_httpserver/pytest_plugin.py index f19ec58..4ba9d86 100644 --- a/pytest_httpserver/pytest_plugin.py +++ b/pytest_httpserver/pytest_plugin.py @@ -5,6 +5,7 @@ import pytest +from .httpserver import ExtraOptions from .httpserver import HTTPServer if TYPE_CHECKING: @@ -44,10 +45,16 @@ def httpserver_ssl_context() -> None: return None +@pytest.fixture(scope="session") +def httpserver_extra_options() -> ExtraOptions: + return ExtraOptions() + + @pytest.fixture(scope="session") def make_httpserver( httpserver_listen_address: tuple[str | None, int | None], httpserver_ssl_context: SSLContext | None, + httpserver_extra_options: ExtraOptions, ) -> Generator[HTTPServer, None, None]: host, port = httpserver_listen_address if not host: @@ -55,7 +62,12 @@ def make_httpserver( if not port: port = HTTPServer.DEFAULT_LISTEN_PORT - server = HTTPServer(host=host, port=port, ssl_context=httpserver_ssl_context) + server = HTTPServer.with_extra_options( + host=host, + port=port, + ssl_context=httpserver_ssl_context, + extra_options=httpserver_extra_options, + ) server.start() yield server server.clear() @@ -80,8 +92,14 @@ def httpserver(make_httpserver: HTTPServer) -> HTTPServer: @pytest.fixture(scope="session") def make_httpserver_ipv4( httpserver_ssl_context: SSLContext | None, + httpserver_extra_options: ExtraOptions, ) -> Generator[HTTPServer, None, None]: - server = HTTPServer(host="127.0.0.1", port=0, ssl_context=httpserver_ssl_context) + server = HTTPServer.with_extra_options( + host="127.0.0.1", + port=0, + ssl_context=httpserver_ssl_context, + extra_options=httpserver_extra_options, + ) server.start() yield server server.clear() @@ -99,8 +117,14 @@ def httpserver_ipv4(make_httpserver_ipv4: HTTPServer) -> HTTPServer: @pytest.fixture(scope="session") def make_httpserver_ipv6( httpserver_ssl_context: SSLContext | None, + httpserver_extra_options: ExtraOptions, ) -> Generator[HTTPServer, None, None]: - server = HTTPServer(host="::1", port=0, ssl_context=httpserver_ssl_context) + server = HTTPServer.with_extra_options( + host="::1", + port=0, + ssl_context=httpserver_ssl_context, + extra_options=httpserver_extra_options, + ) server.start() yield server server.clear() diff --git a/tests/test_extra_options.py b/tests/test_extra_options.py new file mode 100644 index 0000000..56b7ded --- /dev/null +++ b/tests/test_extra_options.py @@ -0,0 +1,41 @@ +from inspect import signature + +from pytest_httpserver import ExtraOptions +from pytest_httpserver import HTTPServer +from pytest_httpserver import WaitingSettings + + +def test_httpserver_extra_options(): + extra_options = ExtraOptions( + startup_timeout=60, + threaded=True, + default_waiting_settings=WaitingSettings(timeout=5), + ) + + server = HTTPServer.with_extra_options( + host="localhost", + port=0, + ssl_context=None, + extra_options=extra_options, + ) + + assert server.startup_timeout == 60 + assert server.threaded is True + assert server.default_waiting_settings.timeout == 5 + + +def test_constructor_signature_matches_with_extra_options(): + # compares that ExtraOptions default values matches with HTTPServer + # constructor defaults + + httpserver_sig = signature(HTTPServer.__init__) + extra_options_sig = signature(ExtraOptions.__init__) + for param_name, extra_param in extra_options_sig.parameters.items(): + if param_name == "self": + continue + httpserver_param = httpserver_sig.parameters.get(param_name) + assert httpserver_param is not None, f"Parameter {param_name} missing in HTTPServer" + assert httpserver_param.default == extra_param.default, ( + f"Default value for parameter {param_name} does not match: " + f"{httpserver_param.default} != {extra_param.default}" + ) diff --git a/tests/test_release.py b/tests/test_release.py index 8643d11..e5cfdfb 100644 --- a/tests/test_release.py +++ b/tests/test_release.py @@ -219,6 +219,7 @@ def test_sdist_contents(build: Build, version: str): "examples", "test_bake.py", "test_blocking_httpserver.py", + "test_extra_options.py", "test_handler_errors.py", "test_headers.py", "test_hooks.py", From 534d909a5ccc6e4c85713d27156352d3dc6b1397 Mon Sep 17 00:00:00 2001 From: Cserna Zsolt Date: Sat, 14 Feb 2026 12:58:46 +0100 Subject: [PATCH 2/4] rename extra options to server options --- pytest_httpserver/__init__.py | 4 ++-- pytest_httpserver/httpserver.py | 12 ++++------ pytest_httpserver/pytest_plugin.py | 24 +++++++++---------- ...{test_extra_options.py => test_options.py} | 20 ++++++++-------- tests/test_release.py | 2 +- 5 files changed, 30 insertions(+), 32 deletions(-) rename tests/{test_extra_options.py => test_options.py} (62%) diff --git a/pytest_httpserver/__init__.py b/pytest_httpserver/__init__.py index d132ecf..00d5cf1 100644 --- a/pytest_httpserver/__init__.py +++ b/pytest_httpserver/__init__.py @@ -10,7 +10,6 @@ "BlockingHTTPServer", "BlockingRequestHandler", "Error", - "ExtraOptions", "HTTPServer", "HTTPServerError", "HeaderValueMatcher", @@ -18,6 +17,7 @@ "RequestHandler", "RequestMatcher", "RequestMatcherKwargs", + "ServerOptions", "URIPattern", "WaitingSettings", ] @@ -28,7 +28,6 @@ from .httpserver import METHOD_ALL from .httpserver import URI_DEFAULT from .httpserver import Error -from .httpserver import ExtraOptions from .httpserver import HeaderValueMatcher from .httpserver import HTTPServer from .httpserver import HTTPServerError @@ -36,5 +35,6 @@ from .httpserver import RequestHandler from .httpserver import RequestMatcher from .httpserver import RequestMatcherKwargs +from .httpserver import ServerOptions from .httpserver import URIPattern from .httpserver import WaitingSettings diff --git a/pytest_httpserver/httpserver.py b/pytest_httpserver/httpserver.py index 19eb1e9..275ec69 100644 --- a/pytest_httpserver/httpserver.py +++ b/pytest_httpserver/httpserver.py @@ -949,7 +949,7 @@ def format_host(host: str) -> str: @dataclass -class ExtraOptions: +class ServerOptions: default_waiting_settings: WaitingSettings | None = None threaded: bool = False startup_timeout: float | None = None @@ -1010,16 +1010,14 @@ def __init__( self._readiness_check_pending = False @classmethod - def with_extra_options( - cls, host: str, port: int, ssl_context: SSLContext | None, extra_options: ExtraOptions - ) -> Self: + def with_options(cls, host: str, port: int, ssl_context: SSLContext | None, options: ServerOptions) -> Self: return cls( host, port, ssl_context, - default_waiting_settings=extra_options.default_waiting_settings, - threaded=extra_options.threaded, - startup_timeout=extra_options.startup_timeout, + default_waiting_settings=options.default_waiting_settings, + threaded=options.threaded, + startup_timeout=options.startup_timeout, ) def start(self) -> None: diff --git a/pytest_httpserver/pytest_plugin.py b/pytest_httpserver/pytest_plugin.py index 4ba9d86..0db4cda 100644 --- a/pytest_httpserver/pytest_plugin.py +++ b/pytest_httpserver/pytest_plugin.py @@ -5,8 +5,8 @@ import pytest -from .httpserver import ExtraOptions from .httpserver import HTTPServer +from .httpserver import ServerOptions if TYPE_CHECKING: from collections.abc import Generator @@ -46,15 +46,15 @@ def httpserver_ssl_context() -> None: @pytest.fixture(scope="session") -def httpserver_extra_options() -> ExtraOptions: - return ExtraOptions() +def httpserver_options() -> ServerOptions: + return ServerOptions() @pytest.fixture(scope="session") def make_httpserver( httpserver_listen_address: tuple[str | None, int | None], httpserver_ssl_context: SSLContext | None, - httpserver_extra_options: ExtraOptions, + httpserver_options: ServerOptions, ) -> Generator[HTTPServer, None, None]: host, port = httpserver_listen_address if not host: @@ -62,11 +62,11 @@ def make_httpserver( if not port: port = HTTPServer.DEFAULT_LISTEN_PORT - server = HTTPServer.with_extra_options( + server = HTTPServer.with_options( host=host, port=port, ssl_context=httpserver_ssl_context, - extra_options=httpserver_extra_options, + options=httpserver_options, ) server.start() yield server @@ -92,13 +92,13 @@ def httpserver(make_httpserver: HTTPServer) -> HTTPServer: @pytest.fixture(scope="session") def make_httpserver_ipv4( httpserver_ssl_context: SSLContext | None, - httpserver_extra_options: ExtraOptions, + httpserver_options: ServerOptions, ) -> Generator[HTTPServer, None, None]: - server = HTTPServer.with_extra_options( + server = HTTPServer.with_options( host="127.0.0.1", port=0, ssl_context=httpserver_ssl_context, - extra_options=httpserver_extra_options, + options=httpserver_options, ) server.start() yield server @@ -117,13 +117,13 @@ def httpserver_ipv4(make_httpserver_ipv4: HTTPServer) -> HTTPServer: @pytest.fixture(scope="session") def make_httpserver_ipv6( httpserver_ssl_context: SSLContext | None, - httpserver_extra_options: ExtraOptions, + httpserver_options: ServerOptions, ) -> Generator[HTTPServer, None, None]: - server = HTTPServer.with_extra_options( + server = HTTPServer.with_options( host="::1", port=0, ssl_context=httpserver_ssl_context, - extra_options=httpserver_extra_options, + options=httpserver_options, ) server.start() yield server diff --git a/tests/test_extra_options.py b/tests/test_options.py similarity index 62% rename from tests/test_extra_options.py rename to tests/test_options.py index 56b7ded..f563cd4 100644 --- a/tests/test_extra_options.py +++ b/tests/test_options.py @@ -1,22 +1,22 @@ from inspect import signature -from pytest_httpserver import ExtraOptions from pytest_httpserver import HTTPServer +from pytest_httpserver import ServerOptions from pytest_httpserver import WaitingSettings -def test_httpserver_extra_options(): - extra_options = ExtraOptions( +def test_httpserver_options(): + options = ServerOptions( startup_timeout=60, threaded=True, default_waiting_settings=WaitingSettings(timeout=5), ) - server = HTTPServer.with_extra_options( + server = HTTPServer.with_options( host="localhost", port=0, ssl_context=None, - extra_options=extra_options, + options=options, ) assert server.startup_timeout == 60 @@ -25,17 +25,17 @@ def test_httpserver_extra_options(): def test_constructor_signature_matches_with_extra_options(): - # compares that ExtraOptions default values matches with HTTPServer + # compares that ServerOptions default values matches with HTTPServer # constructor defaults httpserver_sig = signature(HTTPServer.__init__) - extra_options_sig = signature(ExtraOptions.__init__) - for param_name, extra_param in extra_options_sig.parameters.items(): + options_sig = signature(ServerOptions.__init__) + for param_name, options_param in options_sig.parameters.items(): if param_name == "self": continue httpserver_param = httpserver_sig.parameters.get(param_name) assert httpserver_param is not None, f"Parameter {param_name} missing in HTTPServer" - assert httpserver_param.default == extra_param.default, ( + assert httpserver_param.default == options_param.default, ( f"Default value for parameter {param_name} does not match: " - f"{httpserver_param.default} != {extra_param.default}" + f"{httpserver_param.default} != {options_param.default}" ) diff --git a/tests/test_release.py b/tests/test_release.py index e5cfdfb..2203aab 100644 --- a/tests/test_release.py +++ b/tests/test_release.py @@ -219,7 +219,7 @@ def test_sdist_contents(build: Build, version: str): "examples", "test_bake.py", "test_blocking_httpserver.py", - "test_extra_options.py", + "test_options.py", "test_handler_errors.py", "test_headers.py", "test_hooks.py", From 1333c66c4fa17a7da68daabb78530fe7b366aaa0 Mon Sep 17 00:00:00 2001 From: Zsolt Cserna Date: Wed, 18 Feb 2026 07:27:44 +0100 Subject: [PATCH 3/4] Update tests/test_options.py Co-authored-by: Hayao --- tests/test_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_options.py b/tests/test_options.py index f563cd4..dab8111 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -24,7 +24,7 @@ def test_httpserver_options(): assert server.default_waiting_settings.timeout == 5 -def test_constructor_signature_matches_with_extra_options(): +def test_constructor_signature_matches_with_server_options() -> None: # compares that ServerOptions default values matches with HTTPServer # constructor defaults From 585d165a2f10ce8f76f8011d6f44d89eb85224d4 Mon Sep 17 00:00:00 2001 From: Zsolt Cserna Date: Wed, 18 Feb 2026 07:27:59 +0100 Subject: [PATCH 4/4] Update tests/test_options.py Co-authored-by: Hayao --- tests/test_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_options.py b/tests/test_options.py index dab8111..2b6d9e8 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -5,7 +5,7 @@ from pytest_httpserver import WaitingSettings -def test_httpserver_options(): +def test_httpserver_options() -> None: options = ServerOptions( startup_timeout=60, threaded=True,