diff --git a/tests/integration/test_integration_accounts.py b/tests/integration/test_integration_accounts.py index 6e97e94..9eda751 100644 --- a/tests/integration/test_integration_accounts.py +++ b/tests/integration/test_integration_accounts.py @@ -1,9 +1,9 @@ from tests.integration import asserts -def test_accounts_list(api): - services = api.accounts.list() - assert len(services) >= 1 +def test_accounts_list(api, account): + accounts = api.accounts.list() + assert len(accounts) >= 1 def test_account_can_be_created(api, account, account_params): @@ -22,3 +22,6 @@ def test_account_can_be_read_by_name(api, account, account_params): read = api.accounts[account_name] asserts.assert_resource(read) asserts.assert_resource_params(read, account_params) + +def test_users_list(api, account): + assert len(account.users.list()) >= 1 diff --git a/tests/integration/test_integration_application.py b/tests/integration/test_integration_application.py index d34a1da..2195131 100644 --- a/tests/integration/test_integration_application.py +++ b/tests/integration/test_integration_application.py @@ -16,7 +16,7 @@ def test_application_can_be_created(application, application_params): asserts.assert_resource_params(application, application_params) -def test_application_list(account): +def test_application_list(account, application): applications = account.applications.list() assert len(applications) > 0 diff --git a/tests/integration/test_integration_backend_mapping_rules.py b/tests/integration/test_integration_backend_mapping_rules.py index c09f1c3..98c1a80 100644 --- a/tests/integration/test_integration_backend_mapping_rules.py +++ b/tests/integration/test_integration_backend_mapping_rules.py @@ -4,8 +4,8 @@ def test_should_list_mapping_rules(backend, backend_mapping_rule): - resource = backend.mapping_rules.list() - assert resource + resources = backend.mapping_rules.list() + assert len(resources) >= 1 def test_should_create_mapping_rule(backend_mapping_rule, backend_mapping_rule_params): asserts.assert_resource(backend_mapping_rule) diff --git a/tests/integration/test_integration_backend_metrics.py b/tests/integration/test_integration_backend_metrics.py index 4764883..56f631b 100644 --- a/tests/integration/test_integration_backend_metrics.py +++ b/tests/integration/test_integration_backend_metrics.py @@ -45,9 +45,9 @@ def test_should_delete_metric(backend, backend_updated_metric_params): assert not resource.exists() -def test_should_list_metrics(backend): +def test_should_list_metrics(backend, backend_metric): resources = backend.metrics.list() - assert len(resources) > 1 + assert len(resources) >= 1 def test_should_apicast_return_403_when_metric_is_disabled( service, backend_metric_params, create_backend_mapping_rule, diff --git a/tests/integration/test_integration_backends.py b/tests/integration/test_integration_backends.py index ba88751..8ed4eba 100644 --- a/tests/integration/test_integration_backends.py +++ b/tests/integration/test_integration_backends.py @@ -9,7 +9,7 @@ def test_3scale_url_is_set(api, url, token): assert api.url is not None -def test_backends_list(api): +def test_backends_list(api, backend): backends = api.backends.list() assert len(backends) >= 1 diff --git a/tests/integration/test_integration_methods.py b/tests/integration/test_integration_methods.py index 7f09f73..7256a50 100644 --- a/tests/integration/test_integration_methods.py +++ b/tests/integration/test_integration_methods.py @@ -4,6 +4,8 @@ from tests.integration import asserts +def test_list_methods(metric, method): + assert len(metric.methods.list()) >= 1 def test_should_create_method(method, method_params): asserts.assert_resource(method) diff --git a/tests/integration/test_integration_metrics.py b/tests/integration/test_integration_metrics.py index ae3fbac..8e62672 100644 --- a/tests/integration/test_integration_metrics.py +++ b/tests/integration/test_integration_metrics.py @@ -5,6 +5,8 @@ from tests.integration import asserts +def test_list_metrics(service, metric): + assert len(service.metrics.list()) >= 1 def test_should_create_metric(metric, metric_params): asserts.assert_resource(metric) diff --git a/tests/integration/test_integration_services.py b/tests/integration/test_integration_services.py index ae60cd9..157ad1d 100644 --- a/tests/integration/test_integration_services.py +++ b/tests/integration/test_integration_services.py @@ -9,7 +9,7 @@ def test_3scale_url_is_set(api, url, token): assert api.url is not None -def test_services_list(api): +def test_services_list(api, service): services = api.services.list() assert len(services) >= 1 @@ -107,6 +107,9 @@ def test_service_mapping_rules(service): map_rules = service.mapping_rules.list() assert len(map_rules) >= 1 +def test_service_backend_usages_list(service, backend_usage): + back_usages = service.backend_usages.list() + assert len(back_usages) >= 1 def test_service_backend_usages_backend(backend_usage, backend): assert backend_usage.backend.entity_id == backend.entity_id diff --git a/threescale_api/defaults.py b/threescale_api/defaults.py index 374d67d..500f1a5 100644 --- a/threescale_api/defaults.py +++ b/threescale_api/defaults.py @@ -386,6 +386,51 @@ def _invalidate(self): self._entity = None +class DefaultPaginationClient(DefaultClient): + """ Client to handle API endpoints with pagination. + List of endpoints supporting pagination with per_page size: + - accounts 500 + limits per app plan 50 - not implemented in client + application list for all services 500 - not implemented in client + - backend mapping rules 500 + - backend method list 500 + - backend metric 500 + - backend 500 + - service 500 + invoice list by account 20 - not implemented by standard "list" method + - invoice list 20 + - all cms 100 + """ + def __init__(self, *args, per_page=500, **kwargs): + self.per_page = per_page + super().__init__(*args, **kwargs) + + def _list(self, **kwargs): + """ List all objects via paginated API endpoint """ + kwargs = kwargs.copy() + kwargs.setdefault("params", {}) + if "page" in kwargs["params"] or self.per_page is None: + return super()._list(**kwargs) + pagenum = 1 + + kwargs["params"]["page"] = pagenum + kwargs["params"]["per_page"] = self.per_page + + page = super()._list(**kwargs) + ret_list = page + + while len(page): + pagenum += 1 + kwargs["params"]["page"] = pagenum + page = super()._list(**kwargs) + ret_list += page + + return ret_list + + def __iter__(self): + return self._list() + + class DefaultPlanClient(DefaultClient): def set_default(self, entity_id: int, **kwargs) -> 'DefaultPlanResource': """Sets default plan for the entity @@ -429,7 +474,7 @@ def is_default(self) -> bool: return self['default'] is True -class DefaultStateClient(DefaultClient): +class DefaultStateClient(DefaultPaginationClient): def set_state(self, entity_id, state: str, **kwargs): """Sets the state for the resource Args: diff --git a/threescale_api/resources.py b/threescale_api/resources.py index 07baff3..2c1c938 100644 --- a/threescale_api/resources.py +++ b/threescale_api/resources.py @@ -6,37 +6,41 @@ from threescale_api import utils from threescale_api import errors from threescale_api.defaults import DefaultClient, DefaultPlanClient, DefaultPlanResource, \ - DefaultResource, DefaultStateClient, DefaultUserResource, DefaultStateResource + DefaultResource, DefaultStateClient, DefaultUserResource, DefaultStateResource, \ + DefaultPaginationClient from threescale_api import client log = logging.getLogger(__name__) -class Services(DefaultClient): - def __init__(self, *args, entity_name='service', entity_collection='services', **kwargs): +class Services(DefaultPaginationClient): + def __init__(self, *args, entity_name='service', entity_collection='services', + per_page=500, **kwargs): super().__init__(*args, entity_name=entity_name, - entity_collection=entity_collection, **kwargs) + entity_collection=entity_collection, per_page=per_page, **kwargs) @property def url(self) -> str: return self.threescale_client.admin_api_url + '/services' -class MappingRules(DefaultClient): +class MappingRules(DefaultPaginationClient): def __init__(self, *args, entity_name='mapping_rule', entity_collection='mapping_rules', - **kwargs): + per_page=None, **kwargs): super().__init__(*args, entity_name=entity_name, - entity_collection=entity_collection, **kwargs) + entity_collection=entity_collection, per_page=per_page, + **kwargs) @property def url(self) -> str: return self.parent.url + '/mapping_rules' -class Metrics(DefaultClient): - def __init__(self, *args, entity_name='metric', entity_collection='metrics', **kwargs): +class Metrics(DefaultPaginationClient): + def __init__(self, *args, entity_name='metric', entity_collection='metrics', per_page=None, + **kwargs): super().__init__(*args, entity_name=entity_name, - entity_collection=entity_collection, **kwargs) + entity_collection=entity_collection, per_page=per_page, **kwargs) @property def url(self) -> str: @@ -98,10 +102,11 @@ def url(self) -> str: return self.application_plan.plans_url + f'/metrics/{self.metric.entity_id}/pricing_rules' -class Methods(DefaultClient): - def __init__(self, *args, entity_name='method', entity_collection='methods', **kwargs): +class Methods(DefaultPaginationClient): + def __init__(self, *args, entity_name='method', entity_collection='methods', per_page=None, + **kwargs): super().__init__(*args, entity_name=entity_name, - entity_collection=entity_collection, **kwargs) + entity_collection=entity_collection, per_page=per_page, **kwargs) @property def url(self) -> str: @@ -133,9 +138,10 @@ def url(self) -> str: class AccountUsers(DefaultStateClient): - def __init__(self, *args, entity_name='user', entity_collection='users', **kwargs): + def __init__(self, *args, entity_name='user', entity_collection='users', per_page=None, + **kwargs): super().__init__(*args, entity_name=entity_name, - entity_collection=entity_collection, **kwargs) + entity_collection=entity_collection, per_page=per_page, **kwargs) @property def url(self) -> str: @@ -153,9 +159,11 @@ def url(self) -> str: class Accounts(DefaultStateClient): - def __init__(self, *args, entity_name='account', entity_collection='accounts', **kwargs): + def __init__(self, *args, entity_name='account', entity_collection='accounts', per_page=500, + **kwargs): super().__init__(*args, entity_name=entity_name, - entity_collection=entity_collection, **kwargs) + entity_collection=entity_collection, per_page=per_page, + **kwargs) @property def url(self) -> str: @@ -245,9 +253,9 @@ def pending(self, entity_id, **kwargs) -> 'Account': class Applications(DefaultStateClient): def __init__(self, *args, entity_name='application', entity_collection='applications', - **kwargs): + per_page=None, **kwargs): super().__init__(*args, entity_name=entity_name, - entity_collection=entity_collection, **kwargs) + entity_collection=entity_collection, per_page=per_page, **kwargs) @property def url(self) -> str: @@ -579,56 +587,31 @@ def read(self, params: dict = None, **kwargs) -> dict: return self.rest.get(url=self.url, json=params, **kwargs).json() -class Backends(DefaultClient): +class Backends(DefaultPaginationClient): def __init__(self, *args, entity_name='backend_api', - entity_collection='backend_apis', **kwargs): + entity_collection='backend_apis', per_page=500, **kwargs): super().__init__(*args, entity_name=entity_name, - entity_collection=entity_collection, **kwargs) + entity_collection=entity_collection, per_page=per_page, **kwargs) @property def url(self) -> str: return self.threescale_client.admin_api_url + '/backend_apis' - def list(self, **kwargs): - return list(super().list(**kwargs)) - - def _list(self, **kwargs): - if "page" in kwargs.get("params", {}): - return super()._list(**kwargs) - - pagenum = 1 - - kwargs = kwargs.copy() - if "params" not in kwargs: - kwargs["params"] = {} - - kwargs["params"]["page"] = pagenum - kwargs["params"]["per_page"] = 500 - - page = super()._list(**kwargs) - - while len(page): - for i in page: - yield i - pagenum += 1 - kwargs["params"]["page"] = pagenum - page = super()._list(**kwargs) - - def __iter__(self): - return self._list() - class BackendMetrics(Metrics): - def __init__(self, *args, entity_name='metric', entity_collection='metrics', **kwargs): + def __init__(self, *args, entity_name='metric', entity_collection='metrics', per_page=500, + **kwargs): super().__init__(*args, entity_name=entity_name, - entity_collection=entity_collection, **kwargs) + entity_collection=entity_collection, per_page=per_page, + **kwargs) class BackendMappingRules(MappingRules): def __init__(self, *args, entity_name='mapping_rule', - entity_collection='mapping_rules', **kwargs): + entity_collection='mapping_rules', per_page=500, **kwargs): super().__init__(*args, entity_name=entity_name, - entity_collection=entity_collection, **kwargs) + entity_collection=entity_collection, per_page=per_page, + **kwargs) class BackendUsages(Services): @@ -684,9 +667,10 @@ class ProviderAccountUsers(DefaultStateClient): Client for Provider Accounts. In 3scale, entity under Account Settings > Users """ - def __init__(self, *args, entity_name='user', entity_collection='users', **kwargs): + def __init__(self, *args, entity_name='user', entity_collection='users', per_page=None, + **kwargs): super().__init__(*args, entity_name=entity_name, - entity_collection=entity_collection, **kwargs) + entity_collection=entity_collection, per_page=per_page, **kwargs) @property def url(self) -> str: @@ -842,11 +826,12 @@ class InvoiceState(Enum): OPEN = "open" -class Invoices(DefaultClient): +class Invoices(DefaultPaginationClient): """Default client for Invoices""" - def __init__(self, *args, entity_name='invoice', entity_collection='invoices', **kwargs): + def __init__(self, *args, entity_name='invoice', entity_collection='invoices', + per_page=20, **kwargs): super().__init__(*args, entity_name=entity_name, - entity_collection=entity_collection, **kwargs) + entity_collection=entity_collection, per_page=per_page, **kwargs) @property def url(self) -> str: @@ -914,10 +899,10 @@ def url(self) -> str: return self.threescale_client.admin_api_url + '/fields_definitions' -class CmsClient(DefaultClient): +class CmsClient(DefaultPaginationClient): """ Client for all cms api endpoints. """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + def __init__(self, *args, per_page=100, **kwargs): + super().__init__(*args, per_page=per_page, **kwargs) def _extract_resource(self, response, collection) -> Union[List, Dict]: extracted = response.json() @@ -925,32 +910,6 @@ def _extract_resource(self, response, collection) -> Union[List, Dict]: extracted = extracted.get(self._entity_collection) return extracted - def _list(self, **kwargs): - if "page" in kwargs.get("params", {}): - return super()._list(**kwargs) - pagenum = 1 - - kwargs = kwargs.copy() - if "params" not in kwargs: - kwargs["params"] = {} - - kwargs["params"]["page"] = pagenum - kwargs["params"]["per_page"] = 100 - - page = super()._list(**kwargs) - ret_list = page - - while len(page): - pagenum += 1 - kwargs["params"]["page"] = pagenum - page = super()._list(**kwargs) - ret_list += page - - return ret_list - - def __iter__(self): - return self._list() - class CmsFiles(CmsClient): """ Client for files. """ @@ -985,7 +944,7 @@ def url(self) -> str: def publish(self, entity_id, **kwargs): """ Publish template with entity_id """ - log.info("[PUBLISH] " + f"{entity_id}") + log.info("[PUBLISH] %s", entity_id) url = self._entity_url(entity_id) + '/publish' response = self.rest.put(url=url, **kwargs) instance = self._create_instance(response=response) @@ -1080,7 +1039,7 @@ def service(self) -> 'Service': @property def methods(self) -> 'Methods': - return Methods(parent=self, instance_klass=Method) + return Methods(parent=self, instance_klass=Method, per_page=self.client.per_page) class MappingRule(DefaultResource):