Skip to content

Commit

Permalink
Add tests for paginator signature matching (#779)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sirosen authored Jul 11, 2023
1 parent b1b1677 commit e9d639d
Showing 1 changed file with 81 additions and 0 deletions.
81 changes: 81 additions & 0 deletions tests/unit/test_paginator_signature_matching.py
Original file line number Diff line number Diff line change
@@ -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__

0 comments on commit e9d639d

Please sign in to comment.