Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/api/v3/paginator.py b/api/v3/paginator.py
new file mode 100644
index 0000000..16e66fa
--- /dev/null
+++ b/api/v3/paginator.py
@@ -0,0 +1,91 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+from framework import exceptions
+from framework import paginate
+from proto import secrets_pb2
+
+
+def CoercePageSize(page_size, max_size, default_size=None):
+  # type: (int, int, Optional[int]) -> int
+  """Validates page_size and coerces it to max_size if needed.
+
+  Args:
+    page_size: The page_size requested by the user.
+    max_size: the maximum page size allowed. Must be > 0.
+        Also used as default if default_size not provided
+    default_size: default size to use if page_size not provided. Must be > 0.
+
+  Returns:
+    The appropriate page size to use for the request, based on the parameters.
+    Specifically this means
+      - page_size if not greater than max_size
+      - max_size if page_size > max_size
+      - max_size if page_size is not provided and default_size is not provided
+      - default_size if page_size is not provided
+
+  Raises:
+    InputException: if page_size is negative.
+  """
+  # These are programming errors. They are not user input.
+  assert max_size > 0
+  assert default_size is None or default_size > 0
+
+  # Check for invalid user provided page_size.
+  if page_size and page_size < 0:
+    raise exceptions.InputException('`page_size` cannot be negative.')
+
+  if not page_size:
+    return default_size or max_size
+  if page_size > max_size:
+    return max_size
+  return page_size
+
+
+class Paginator(object):
+  """Class to manage API pagination.
+
+  Paginator handles the pagination tasks and info of a single List or
+  Search API method implementation, given the contents of the request.
+  """
+
+  def __init__(self, parent=None, page_size=None, order_by=None,
+      filter_str=None, query=None, projects=None):
+    # type: (Optional[str], Optional[int], Optional[str], Optional[str],
+    #   Optional[str], Optional[Collection[str]]]) -> None
+    self.request_contents = secrets_pb2.ListRequestContents(
+        parent=parent, page_size=page_size, order_by=order_by,
+        filter=filter_str, query=query, projects=projects)
+
+  def GetStart(self, page_token):
+    # type: (Optional[str]) -> int
+    """Validates a request.page_token and returns the start index for it."""
+    if page_token:
+      # TODO(crbug.com/monorail/6758): Proto string fields are unicode types in
+      # python 2. In python 3 these unicode strings will be represented with
+      # string types. paginate.ValidateAndParsePageToken requires a string token
+      # during validation (compare_digest()). Once we move to python 3, we can
+      # remove this string casting.
+      token = str(page_token)
+      return paginate.ValidateAndParsePageToken(token, self.request_contents)
+    return 0
+
+  def GenerateNextPageToken(self, next_start):
+    # type: (Optional[int]) -> str
+    """Generates the `next_page_token` for the API response.
+
+    Args:
+      next_start: The start index of the next page, or None if no more results.
+
+    Returns:
+      A string clients can use to request the next page. Returns None if
+      next_start was None
+    """
+    if next_start is None:
+      return None
+    return paginate.GeneratePageToken(self.request_contents, next_start)