diff --git a/setup.py b/setup.py index 7ebaaf58ca..21bdaf1658 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,6 @@ tests_require = [ 'pytest', 'pytest-httpbin>=0.0.6', - 'pytest-lazy-fixture>=0.0.6', 'responses', 'pytest-mock', 'werkzeug<2.1.0' diff --git a/tests/conftest.py b/tests/conftest.py index 7ca172a867..9430a32ed8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,14 +3,14 @@ import pytest from pytest_httpbin import certs -from .utils import ( # noqa +from .utils import ( # noqa HTTPBIN_WITH_CHUNKED_SUPPORT_DOMAIN, HTTPBIN_WITH_CHUNKED_SUPPORT, REMOTE_HTTPBIN_DOMAIN, IS_PYOPENSSL, mock_env ) -from .utils.plugins_cli import ( # noqa +from .utils.plugins_cli import ( # noqa broken_plugin, dummy_plugin, dummy_plugins, @@ -18,7 +18,9 @@ httpie_plugins_success, interface, ) -from .utils.http_server import http_server, localhost_http_server # noqa +from .utils.http_server import http_server, localhost_http_server # noqa +# noinspection PyUnresolvedReferences +from .fixtures import pytest_lazy_fixture @pytest.fixture(scope='function', autouse=True) diff --git a/tests/fixtures/pytest_lazy_fixture.py b/tests/fixtures/pytest_lazy_fixture.py new file mode 100644 index 0000000000..5b70d8ec1f --- /dev/null +++ b/tests/fixtures/pytest_lazy_fixture.py @@ -0,0 +1,99 @@ +""" +Replacement for the abandoned `pytest.lazy_fixture` + +Based on + +""" +import dataclasses +import typing + +import pytest + + +@dataclasses.dataclass +class LazyFixture: + """Lazy fixture dataclass.""" + + name: str + + +def lazy_fixture(name: str) -> LazyFixture: + """Mark a fixture as lazy.""" + return LazyFixture(name) + + +# NOTE: Mimic the original API +pytest.lazy_fixture = lazy_fixture + + +def is_lazy_fixture(value: object) -> bool: + """Check whether a value is a lazy fixture.""" + return isinstance(value, LazyFixture) + + +def pytest_make_parametrize_id( + config: pytest.Config, + val: object, + argname: str, +) -> str | None: + """Inject lazy fixture parametrized id. + + Reference: + - https://bit.ly/48Off6r + + Args: + config (pytest.Config): pytest configuration. + value (object): fixture value. + argname (str): automatic parameter name. + + Returns: + str: new parameter id. + """ + if is_lazy_fixture(val): + return typing.cast(LazyFixture, val).name + return None + + +@pytest.hookimpl(tryfirst=True) +def pytest_fixture_setup( + fixturedef: pytest.FixtureDef, + request: pytest.FixtureRequest, +) -> object | None: + """Lazy fixture setup hook. + + This hook will never take over a fixture setup but just simply will + try to resolve recursively any lazy fixture found in request.param. + + Reference: + - https://bit.ly/3SyvsXJ + + Args: + fixturedef (pytest.FixtureDef): fixture definition object. + request (pytest.FixtureRequest): fixture request object. + + Returns: + object | None: fixture value or None otherwise. + """ + if hasattr(request, "param") and request.param: + request.param = _resolve_lazy_fixture(request.param, request) + return None + + +def _resolve_lazy_fixture(__val: object, request: pytest.FixtureRequest) -> object: + """Lazy fixture resolver. + + Args: + __val (object): fixture value object. + request (pytest.FixtureRequest): pytest fixture request object. + + Returns: + object: resolved fixture value. + """ + if isinstance(__val, list | tuple): + return tuple(_resolve_lazy_fixture(v, request) for v in __val) + if isinstance(__val, typing.Mapping): + return {k: _resolve_lazy_fixture(v, request) for k, v in __val.items()} + if not is_lazy_fixture(__val): + return __val + lazy_obj = typing.cast(LazyFixture, __val) + return request.getfixturevalue(lazy_obj.name)