From e9d639dc1c260299604305af5edeb26d21cf7b8e Mon Sep 17 00:00:00 2001 From: Stephen Rosen Date: Tue, 11 Jul 2023 10:42:36 -0500 Subject: [PATCH] Add tests for paginator signature matching (#779) These tests enforce that decorated methods of the target client classes satisfy the paginator interfaces for their decorators. For example, if a method is decorated with the MarkerPaginator, then it must take a named argument `marker` for the paginator to use. It's acceptable for the argument to be keyword-only or for it to be positional-or-keyword. But it's not acceptable for it to be, e.g., positional-only. --- .../unit/test_paginator_signature_matching.py | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 tests/unit/test_paginator_signature_matching.py diff --git a/tests/unit/test_paginator_signature_matching.py b/tests/unit/test_paginator_signature_matching.py new file mode 100644 index 000000000..e22cb0dcd --- /dev/null +++ b/tests/unit/test_paginator_signature_matching.py @@ -0,0 +1,81 @@ +""" +Inspect the signatures of paginated methods and compare them against their +attached paginator requirements. +""" +import inspect + +import pytest + +import globus_sdk +from globus_sdk.paging import ( + HasNextPaginator, + LastKeyPaginator, + LimitOffsetTotalPaginator, + MarkerPaginator, + NextTokenPaginator, + NullableMarkerPaginator, +) + +_CLIENTS_TO_CHECK = ( + # alphabetical by service name + # Auth + globus_sdk.AuthClient, + globus_sdk.NativeAppAuthClient, + globus_sdk.ConfidentialAppAuthClient, + # Flows + globus_sdk.FlowsClient, + globus_sdk.SpecificFlowClient, + # GCS + globus_sdk.GCSClient, + # Groups + globus_sdk.GroupsClient, + # Search + globus_sdk.SearchClient, + # Timers + globus_sdk.TimerClient, + # Transfer + globus_sdk.TransferClient, +) + +_METHODS_TO_CHECK = [] +for cls in _CLIENTS_TO_CHECK: + methods = inspect.getmembers(cls, predicate=inspect.isfunction) + for name, value in methods: + if name.startswith("_"): + continue + # inherited, non-overloaded methods + if name not in cls.__dict__: + continue + if getattr(value, "_has_paginator", False): + _METHODS_TO_CHECK.append(value) + + +@pytest.mark.parametrize("method", _METHODS_TO_CHECK) +def test_paginated_method_matches_paginator_requirements(method): + paginator_class = method._paginator_class + + sig = inspect.signature(method) + kwarg_names = { + p.name + for p in sig.parameters.values() + if p.kind in (p.POSITIONAL_OR_KEYWORD, p.KEYWORD_ONLY) + } + + if ( + paginator_class is HasNextPaginator + or paginator_class is LimitOffsetTotalPaginator + ): + expect_params = ("limit", "offset") + elif ( + paginator_class is MarkerPaginator or paginator_class is NullableMarkerPaginator + ): + expect_params = ("marker",) + elif paginator_class is LastKeyPaginator: + expect_params = ("last_key",) + elif paginator_class is NextTokenPaginator: + expect_params = ("next_token",) + else: + raise NotImplementedError(f"unrecognized paginator class: {paginator_class}") + + for param_name in expect_params: + assert param_name in kwarg_names, method.__qualname__