diff --git a/docs/rest-api.yml b/docs/rest-api.yml index f8a283c5..d7de04da 100644 --- a/docs/rest-api.yml +++ b/docs/rest-api.yml @@ -108,7 +108,7 @@ paths: description: | Return a finite number of users. Can be used with offset to paginate. - If unspecified, return all users. + If unspecified, use api_page_default_limit. responses: "200": description: The Hub's user list @@ -455,7 +455,7 @@ paths: description: | Return a finite number of groups. Can be used with offset to paginate. - If unspecified, return all groups. + If unspecified, use api_page_default_limit. responses: "200": description: The list of groups @@ -487,7 +487,7 @@ paths: description: | Return a finite number of users within the given group. Can be used with offset to paginate. - If unspecified, return all users. + If unspecified, use api_page_default_limit responses: "200": description: The group model @@ -610,7 +610,7 @@ paths: description: | Return a finite number of routes. Can be used with offset to paginate. - If unspecified, return all routes. + If unspecified, use api_page_default_limit responses: "200": description: Routing table diff --git a/docs/source/reference/rest.md b/docs/source/reference/rest.md index 1356df09..acd849ba 100644 --- a/docs/source/reference/rest.md +++ b/docs/source/reference/rest.md @@ -175,6 +175,8 @@ r.raise_for_status() r.json() ``` +By default, pagination limit will be specified by the `JupyterHub.api_page_default_limit` config variable. + Pagination is enabled on the `GET /users`, `GET /groups`, `GET /groups/{name}`, and `GET /proxy` REST endpoints. ## Enabling users to spawn multiple named-servers via the API diff --git a/jupyterhub/apihandlers/groups.py b/jupyterhub/apihandlers/groups.py index 31bbd2e5..70c18039 100644 --- a/jupyterhub/apihandlers/groups.py +++ b/jupyterhub/apihandlers/groups.py @@ -39,7 +39,8 @@ class GroupListAPIHandler(_GroupAPIHandler): def get(self): """List groups""" offset = self.get_argument("offset", None) - limit = self.get_argument("limit", None) + limit = self.get_argument("limit", self.settings['app'].api_page_default_limit) + max_limit = self.settings['app'].api_page_max_limit query = self.db.query(orm.Group) @@ -52,14 +53,18 @@ class GroupListAPIHandler(_GroupAPIHandler): ) query = query.offset(offset) - if limit is not None: + if limit != self.settings['app'].api_page_default_limit: try: limit = int(limit) + if limit > max_limit: + limit = max_limit + except Exception as e: raise web.HTTPError( 400, "Invalid argument type, limit must be an integer" ) - query = query.limit(int(limit)) + + query = query.limit(limit) data = [self.group_model(group) for group in query] self.write(json.dumps(data)) @@ -100,13 +105,12 @@ class GroupAPIHandler(_GroupAPIHandler): @admin_only def get(self, name): offset = self.get_argument("offset", None) - limit = self.get_argument("limit", None) + limit = self.get_argument("limit", self.settings['app'].api_page_default_limit) + max_limit = self.settings['app'].api_page_max_limit + group = self.find_group(name) users_slice = None - print(type(group.users)) - print(group.users) - if offset is not None: try: offset = int(offset) @@ -117,15 +121,17 @@ class GroupAPIHandler(_GroupAPIHandler): group.users = group.users[offset:] - if limit is not None: + if limit != self.settings['app'].api_page_default_limit: try: limit = int(limit) + if limit > max_limit: + limit = max_limit except Exception as e: raise web.HTTPError( 400, "Invalid argument type, limit must be an integer" ) - group.users = group.users[:limit] + group.users = group.users[:limit] group_model = self.group_model(group) diff --git a/jupyterhub/apihandlers/proxy.py b/jupyterhub/apihandlers/proxy.py index 06c20319..8bbbafb3 100644 --- a/jupyterhub/apihandlers/proxy.py +++ b/jupyterhub/apihandlers/proxy.py @@ -18,7 +18,8 @@ class ProxyAPIHandler(APIHandler): but without clients needing to maintain separate """ offset = self.get_argument("offset", None) - limit = self.get_argument("limit", None) + limit = self.get_argument("limit", self.settings['app'].api_page_default_limit) + max_limit = self.settings['app'].api_page_max_limit routes = await self.proxy.get_all_routes() @@ -35,16 +36,18 @@ class ProxyAPIHandler(APIHandler): if key in routes } - if limit is not None: + if limit != self.settings['app'].api_page_default_limit: try: limit = int(limit) + if limit > max_limit: + limit = max_limit except Exception as e: raise web.HTTPError( 400, "Invalid argument type, limit must be an integer" ) - routes = { - key: routes[key] for key in list(routes.keys())[:limit] if key in routes - } + routes = { + key: routes[key] for key in list(routes.keys())[:limit] if key in routes + } self.write(json.dumps(routes)) diff --git a/jupyterhub/apihandlers/users.py b/jupyterhub/apihandlers/users.py index 38f56a16..2c01bf70 100644 --- a/jupyterhub/apihandlers/users.py +++ b/jupyterhub/apihandlers/users.py @@ -56,7 +56,8 @@ class UserListAPIHandler(APIHandler): def get(self): state_filter = self.get_argument("state", None) offset = self.get_argument("offset", None) - limit = self.get_argument("limit", None) + limit = self.get_argument("limit", self.settings['app'].api_page_default_limit) + max_limit = self.settings['app'].api_page_max_limit # post_filter post_filter = None @@ -99,10 +100,25 @@ class UserListAPIHandler(APIHandler): # Apply offset and limit for request pagination if offset is not None: - query = query.offset(int(offset)) + try: + offset = int(offset) + except Exception as e: + raise web.HTTPError( + 400, "Invalid argument type, offset must be an integer" + ) + query = query.offset(offset) - if limit is not None: - query = query.limit(int(limit)) + if limit != self.settings['app'].api_page_default_limit: + try: + limit = int(limit) + if limit > max_limit: + limit = max_limit + except Exception as e: + raise web.HTTPError( + 400, "Invalid argument type, limit must be an integer" + ) + + query = query.limit(limit) data = [ self.user_model(u, include_servers=True, include_state=True) diff --git a/jupyterhub/app.py b/jupyterhub/app.py index 4fc493c2..affab849 100644 --- a/jupyterhub/app.py +++ b/jupyterhub/app.py @@ -936,6 +936,15 @@ class JupyterHub(Application): """, ).tag(config=True) + api_page_default_limit = Integer( + 50, + help="The default amount of records returned by a paginated endpoint", + ).tag(config=True) + + api_page_max_limit = Integer( + 200, help="The maximum amount of records that can be returned at once" + ) + authenticate_prometheus = Bool( True, help="Authentication for prometheus metrics" ).tag(config=True)