From 2bef229258265aaa4825ee4b5075bd739b1cd6e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20=C3=96qvist?= Date: Thu, 2 Nov 2023 14:52:45 +0100 Subject: [PATCH] rest: Add pagination support --- nipap/nipap/backend.py | 28 +++++++++++++++++++++------- nipap/nipap/rest.py | 12 ++++++++---- tests/test_rest.py | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/nipap/nipap/backend.py b/nipap/nipap/backend.py index 09093e418..626720f47 100644 --- a/nipap/nipap/backend.py +++ b/nipap/nipap/backend.py @@ -449,6 +449,19 @@ # read-only from _prefix_spec _prefix_attrs = {k: v for k, v in _prefix_spec.items() if not _prefix_spec[k]['ro']} + +# list of all available search options on a prefix +prefix_search_options_spec = { + 'parents_depth': 0, + 'children_depth': 0, + 'include_all_parents': False, + 'include_all_children': False, + 'include_neighbors': False, + 'max_result': 50, + 'offset': 0 +} + + # list of all attributes on a vrf, including both writable and read-only values _vrf_spec = { 'avps': { @@ -3091,6 +3104,7 @@ def search_prefix(self, auth, query, search_options=None): * :attr:`children_depth` - How many levels of children to return. Set to :data:`-1` to include all children. * :attr:`include_all_parents` - Include all parents, no matter what depth is specified. * :attr:`include_all_children` - Include all children, no matter what depth is specified. + * :attr:`include_neighbors` - Include neighbors. * :attr:`max_result` - The maximum number of prefixes to return (default :data:`50`). * :attr:`offset` - Offset the result list this many prefixes (default :data:`0`). @@ -3124,7 +3138,7 @@ def search_prefix(self, auth, query, search_options=None): # include_parents if 'include_all_parents' not in search_options: - search_options['include_all_parents'] = False + search_options['include_all_parents'] = prefix_search_options_spec['include_all_parents'] else: if search_options['include_all_parents'] not in (True, False): raise NipapValueError( @@ -3133,7 +3147,7 @@ def search_prefix(self, auth, query, search_options=None): # include_children if 'include_all_children' not in search_options: - search_options['include_all_children'] = False + search_options['include_all_children'] = prefix_search_options_spec['include_all_children'] else: if search_options['include_all_children'] not in (True, False): raise NipapValueError("Invalid value for option 'include_all_children'. Only true and false valid. " @@ -3141,7 +3155,7 @@ def search_prefix(self, auth, query, search_options=None): # parents_depth if 'parents_depth' not in search_options: - search_options['parents_depth'] = 0 + search_options['parents_depth'] = prefix_search_options_spec['parents_depth'] else: try: search_options['parents_depth'] = int(search_options['parents_depth']) @@ -3150,7 +3164,7 @@ def search_prefix(self, auth, query, search_options=None): # children_depth if 'children_depth' not in search_options: - search_options['children_depth'] = 0 + search_options['children_depth'] = prefix_search_options_spec['children_depth'] else: try: search_options['children_depth'] = int(search_options['children_depth']) @@ -3159,7 +3173,7 @@ def search_prefix(self, auth, query, search_options=None): # include_neighbors if 'include_neighbors' not in search_options: - search_options['include_neighbors'] = False + search_options['include_neighbors'] = prefix_search_options_spec['include_neighbors'] else: if search_options['include_neighbors'] not in (True, False): raise NipapValueError("Invalid value for option 'include_neighbors'. Only true and false valid. " @@ -3167,7 +3181,7 @@ def search_prefix(self, auth, query, search_options=None): # max_result if 'max_result' not in search_options: - search_options['max_result'] = 50 + search_options['max_result'] = prefix_search_options_spec['max_result'] else: if search_options['max_result'] in (False, None): search_options['max_result'] = None @@ -3179,7 +3193,7 @@ def search_prefix(self, auth, query, search_options=None): # offset if 'offset' not in search_options: - search_options['offset'] = 0 + search_options['offset'] = prefix_search_options_spec['offset'] else: try: search_options['offset'] = int(search_options['offset']) diff --git a/nipap/nipap/rest.py b/nipap/nipap/rest.py index 10d3a9fb5..12ea55804 100644 --- a/nipap/nipap/rest.py +++ b/nipap/nipap/rest.py @@ -14,7 +14,7 @@ from flask import Flask, request, Response, got_request_exception, jsonify from flask_restful import Resource, Api, abort -from .backend import Nipap, NipapError +from .backend import Nipap, NipapError, prefix_search_options_spec import nipap from .authlib import AuthFactory, AuthError @@ -219,11 +219,15 @@ def get(self, args): query = args.get('prefix') search_query = {} + search_options = {} if query is not None: # Create search query dict from request params query_parts = [] - for field, search_value in list(query.items()): - query_parts.append(get_query_for_field(field, search_value)) + for field, value in list(query.items()): + if field in prefix_search_options_spec.keys(): + search_options[field] = value + else: + query_parts.append(get_query_for_field(field, value)) search_query = query_parts[0] for query_part in query_parts[1:]: search_query = { @@ -233,7 +237,7 @@ def get(self, args): } try: - result = self.nip.search_prefix(args.get('auth'), search_query) + result = self.nip.search_prefix(args.get('auth'), search_query, search_options) # mangle result result['result'] = [ _mangle_prefix(prefix) for prefix in result['result'] ] diff --git a/tests/test_rest.py b/tests/test_rest.py index 7532e386e..83c628e2c 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -481,6 +481,45 @@ def test_prefix_search_error_message(self): self.assertTrue(get_prefix_request.text.__contains__('\'prefixeere\' unknown')) + def test_prefix_pagination(self): + """ Add prefixes with the same order_id, expect different number of prefixes in result + using max_result and offset + """ + + # add test prefixes 1.33.[0-59].0/24 + attr = {} + attr['description'] = 'test edit prefix' + attr['type'] = 'assignment' + attr['order_id'] = 'test_rest' + for i in range(0, 60): + attr['prefix'] = '1.33.' + str(i) + '.0/24' + self._add_prefix(attr) + + parameters = {'order_id': 'test_rest'} + + # Test default max result of 50 amd offset 0 + get_prefix_request = requests.get(self.server_url, headers=self.headers, params=parameters) + result = json.loads(get_prefix_request.text) + self.assertEqual(50, len(result)) + self.assertEqual(result[0]['prefix'], '1.33.0.0/24') + self.assertEqual(result[49]['prefix'], '1.33.49.0/24') + + # Test max result 100 + parameters['max_result'] = 60 + get_prefix_request = requests.get(self.server_url, headers=self.headers, params=parameters) + result = json.loads(get_prefix_request.text) + self.assertEqual(60, len(result)) + self.assertEqual(result[0]['prefix'], '1.33.0.0/24') + self.assertEqual(result[59]['prefix'], '1.33.59.0/24') + + # Test offset 75 + parameters['offset'] = 35 + get_prefix_request = requests.get(self.server_url, headers=self.headers, params=parameters) + result = json.loads(get_prefix_request.text) + self.assertEqual(25, len(result)) + self.assertEqual(result[0]['prefix'], '1.33.35.0/24') + self.assertEqual(result[24]['prefix'], '1.33.59.0/24') + if __name__ == '__main__':