Skip to content

Allow state handler functions with default arguments in v3 #994

Closed
@lotruheawea

Description

@lotruheawea

Have you read the Contributing Guidelines on issues?

Description

In version 3, state handler functions need to have the arguments as described by StateHandlerFull, StateHandlerNoAction and so on. In function _set_function_state_handler and _state_handler_dict, there are checks on how many parameters there are in the function, e,g. by having len(inspect.signature(f).parameters) != 2 calls.

I would like to replace this by something like this

parameters = inspect.signature(handler).parameters
parameters_without_defaults = {k: v for k, v in parameters.items() if
                                  v.default is v.empty}
...
if len(parameters_without_defaults) != 2:
    ...

This would generate more flexibility in defining state_handler functions. The TypeAliases would need be extended.

Motivation

The current implementation forbids the usage of extra parameters to be passed on to state handler functions. However, this might be a requirement when fixtures are used. E.g. in test_provider in the test_01_fastapi_provider.py, it might not be sufficient to use only the server as a fixture, but another one, where we pass the database object that we want to modify by the state handler. This requirement is definitely there for our use case.

This currently cannot be passed on to the state_handler_function as fixtures are not available in normal functions. It could be relieved by using functools.partial. The test_provider_code could then look like:

def test_provider(server: str, my_fixture) -> None:
    partial_state_handler = partial(
        provider_state_handler, my_extra_arg=my_fixture
    )

    verifier = (
        Verifier("v3_http_provider")
        .add_transport(url=server)
        .add_source("examples/pacts/v3_http_consumer-v3_http_provider.json")
        .state_handler(partial_state_handler, teardown=True)
    )
    verifier.verify()

def provider_state_handler(
    state: str,
    action: str,
    _parameters: dict[str, Any] | None,
    my_extra_arg: Any | None = None
) -> None:
    if action == "setup":
        mapping = {
            "user doesn't exists": mock_user_doesnt_exist,
            "user exists": mock_user_exists,
            "the specified user doesn't exist": mock_post_request_to_create_user,
            "user is present in DB": mock_delete_request_to_delete_user,
        }
        # TODO: there might be a cleaner and more general approach here
        if my_extra_arg is None:
            mapping[state]()
        else:
            mapping[state](my_extra_arg)

    if action == "teardown":
        mapping = {
            "user doesn't exists": verify_user_doesnt_exist_mock,
            "user exists": verify_user_exists_mock,
            "the specified user doesn't exist": verify_mock_post_request_to_create_user,
            "user is present in DB": verify_mock_delete_request_to_delete_user,
        }
        mapping[state]()

I am unsure if this feature has been requested by other users. For us, it is a blocker.

Have you tried building it?

I can definitely create a PR for this, if approved by the maintainers.
I need support on how to run the complete test suite though. I currently still get many

FileNotFoundError: [Errno 2] No such file or directory: './pact-python/tests/v3/compatibility_suite/definition/features/V1/http_consumer.feature'

errors when running hatch run test. The definition subfolder is completely empty.

Also I am unsure how to extend the TypeAliases, to include optional default arguments.

Self-service

  • I'd be willing to contribute this feature to Pact Python myself.

Metadata

Metadata

Assignees

Labels

area:v3Relating to the pact.v3 moduledifficulty:mediumA moderate task requiring a good understanding of the codebasesmartbear-supportedThis issue is supported by SmartBeartype:bugSomething isn't working

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions